From efb424a35af6c295a0e0e5ee9b9ef6a7577f8c86 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 26 Apr 2021 14:49:53 -0700 Subject: [PATCH 01/25] Add CAPI bindings for fixed_decimal --- Cargo.lock | 1 + components/capi/Cargo.toml | 1 + components/capi/src/fixed_decimal.rs | 56 ++++++++++++++++++++++++++++ components/capi/src/lib.rs | 1 + 4 files changed, 59 insertions(+) create mode 100644 components/capi/src/fixed_decimal.rs diff --git a/Cargo.lock b/Cargo.lock index 8e686d367c0..cd43c911702 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -816,6 +816,7 @@ dependencies = [ name = "icu_capi" version = "0.1.0" dependencies = [ + "fixed_decimal", "icu_locid", "icu_plurals", "icu_provider", diff --git a/components/capi/Cargo.toml b/components/capi/Cargo.toml index a9829b65563..b52258e1193 100644 --- a/components/capi/Cargo.toml +++ b/components/capi/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["staticlib", "rlib"] path = "src/lib.rs" [dependencies] +fixed_decimal = { path = "../../utils/fixed_decimal" } icu_locid = { path = "../locid" } icu_plurals = { path = "../plurals/" } icu_provider = { path = "../provider" } diff --git a/components/capi/src/fixed_decimal.rs b/components/capi/src/fixed_decimal.rs new file mode 100644 index 00000000000..0bb69f74eb6 --- /dev/null +++ b/components/capi/src/fixed_decimal.rs @@ -0,0 +1,56 @@ +// 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::FixedDecimal; +use std::ptr; + +/// Opaque type for use behind a pointer, is [`FixedDecimal`] +/// +/// Can be obtained via [`icu4x_fixed_decimal_create()`] and destroyed via [`icu4x_fixed_decimal_destroy()`] +pub type ICU4XFixedDecimal = FixedDecimal; + +#[repr(C)] +/// This is the result returned by [`icu4x_plural_rules_create()`] +pub struct ICU4XCreateFixedDecimalResult { + /// Will be null if `success` is [`false`] + pub decimal: *mut ICU4XFixedDecimal, + /// Currently just a boolean, but we might add a proper error enum as necessary + pub success: bool, +} + +#[no_mangle] +/// FFI version of [`FixedDecimal`]'s constructors. This constructs a [`FixedDecimal`] of the provided +/// `magnitude` and then multiplies it by `10 ^ pow10`. +/// +/// # Safety +/// - Only access `decimal` in the result if `success` is [`true`]. +// +// We can add additional constructors from strings, floats, etc as the need arises +pub extern "C" fn icu4x_fixed_decimal_create( + magnitude: i64, + pow10: i16, +) -> ICU4XCreateFixedDecimalResult { + let fd = FixedDecimal::from(magnitude); + if let Ok(multiplied) = fd.multiplied_pow10(pow10) { + ICU4XCreateFixedDecimalResult { + decimal: Box::into_raw(Box::new(multiplied)), + success: true, + } + } else { + ICU4XCreateFixedDecimalResult { + decimal: ptr::null_mut(), + success: false, + } + } +} + +#[no_mangle] +/// Destructor for [`ICU4XFixedDecimal`] +/// +/// # Safety +/// `fd` must be a pointer to a valid [`ICU4XFixedDecimal`] constructed by +/// [`icu4x_fixed_decimal_create()`]. +pub unsafe extern "C" fn icu4x_fixed_decimal_destroy(fd: *mut ICU4XFixedDecimal) { + let _ = Box::from_raw(fd); +} diff --git a/components/capi/src/lib.rs b/components/capi/src/lib.rs index 7f146844794..99283bb4641 100644 --- a/components/capi/src/lib.rs +++ b/components/capi/src/lib.rs @@ -4,6 +4,7 @@ #![allow(clippy::upper_case_acronyms)] +pub mod fixed_decimal; pub mod locale; pub mod pluralrules; pub mod provider; From a4be6b2c38feed32d7ffc5cca92dbf3b4c8f0d85 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 26 Apr 2021 15:33:03 -0700 Subject: [PATCH 02/25] Add decimals, macro for c-enums --- Cargo.lock | 1 + components/capi/Cargo.toml | 1 + components/capi/src/decimal.rs | 99 ++++++++++++++++++++++++++++++ components/capi/src/lib.rs | 4 ++ components/capi/src/macros.rs | 49 +++++++++++++++ components/capi/src/pluralrules.rs | 50 +++++---------- 6 files changed, 170 insertions(+), 34 deletions(-) create mode 100644 components/capi/src/decimal.rs create mode 100644 components/capi/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index cd43c911702..fefa4557686 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,6 +817,7 @@ name = "icu_capi" version = "0.1.0" dependencies = [ "fixed_decimal", + "icu_decimal", "icu_locid", "icu_plurals", "icu_provider", diff --git a/components/capi/Cargo.toml b/components/capi/Cargo.toml index b52258e1193..33b188af9cf 100644 --- a/components/capi/Cargo.toml +++ b/components/capi/Cargo.toml @@ -16,6 +16,7 @@ path = "src/lib.rs" [dependencies] fixed_decimal = { path = "../../utils/fixed_decimal" } +icu_decimal = { path = "../decimal/" } icu_locid = { path = "../locid" } icu_plurals = { path = "../plurals/" } icu_provider = { path = "../provider" } diff --git a/components/capi/src/decimal.rs b/components/capi/src/decimal.rs new file mode 100644 index 00000000000..935bc2dc59c --- /dev/null +++ b/components/capi/src/decimal.rs @@ -0,0 +1,99 @@ +// 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 crate::locale::ICU4XLocale; +use crate::provider::ICU4XDataProvider; +use icu_decimal::options::{FixedDecimalFormatOptions, GroupingStrategy, SignDisplay}; +use icu_decimal::FixedDecimalFormat; +use std::ptr; + +/// Opaque type for use behind a pointer, is [`FixedDecimalFormat`] +/// +/// Can be obtained via [`icu4x_fixed_decimal_format_create()`] and destroyed via [`icu4x_fixed_decimal_format_destroy()`] +pub type ICU4XFixedDecimalFormat<'d> = FixedDecimalFormat<'d>; + +#[repr(C)] +/// This is the result returned by [`icu4x_fixed_decimal_format_create()`] +pub struct ICU4XCreateFixedDecimalFormatResult<'d> { + /// Will be null if `success` is [`false`] + pub fdf: *mut ICU4XFixedDecimalFormat<'d>, + /// Currently just a boolean, but we might add a proper error enum as necessary + pub success: bool, +} + +#[no_mangle] +/// FFI version of [`FixedDecimalFormat::try_new()`]. See its docs for more details. +/// +/// # Safety +/// - `locale` should be constructed via [`icu4x_locale_create()`](crate::locale::icu4x_locale_create) +/// - `provider` should be constructed via one of the functions in [`crate::locale`](crate::locale) +/// - Only access `fdf` in the result if `success` is [`true`]. +pub extern "C" fn icu4x_fixed_decimal_format_create<'d>( + locale: &ICU4XLocale, + provider: &'d ICU4XDataProvider, + options: ICU4XFixedDecimalFormatOptions, +) -> ICU4XCreateFixedDecimalFormatResult<'d> { + // cheap as long as there are no variants + let langid = locale.as_ref().clone(); + let provider = provider.as_dyn_ref(); + match FixedDecimalFormat::try_new(langid, provider, options.into()) { + Ok(fdf) => { + let fdf = Box::new(fdf); + ICU4XCreateFixedDecimalFormatResult { + fdf: Box::into_raw(fdf), + success: true, + } + } + Err(_) => ICU4XCreateFixedDecimalFormatResult { + fdf: ptr::null_mut(), + success: false, + }, + } +} + +#[no_mangle] +/// Destructor for [`ICU4XFixedDecimalFormat`] +/// +/// # Safety +/// `fdf` must be a pointer to a valid [`ICU4XFixedDecimalFormat`] constructed by +/// [`icu4x_fixed_decimal_format_create()`]. +pub unsafe extern "C" fn icu4x_fixed_decimal_format_destroy(fdf: *mut ICU4XFixedDecimalFormat<'_>) { + let _ = Box::from_raw(fdf); +} + +#[repr(C)] +pub struct ICU4XFixedDecimalFormatOptions { + grouping_strategy: ICU4XGroupingStrategy, + sign_display: ICU4XSignDisplay, +} + +c_enum! { + /// FFI version of [`GroupingStrategy`]. See its docs for more details. + pub c_enum ICU4XGroupingStrategy is #[non_exhaustive] GroupingStrategy { + Auto, + Never, + Always, + Min2, + } +} + +c_enum! { + /// FFI version of [`SignDisplay`]. See its docs for more details. + pub c_enum ICU4XSignDisplay is #[non_exhaustive] SignDisplay { + Auto, + Never, + Always, + ExceptZero, + Negative, + } +} + +impl From for FixedDecimalFormatOptions { + fn from(c: ICU4XFixedDecimalFormatOptions) -> Self { + Self { + grouping_strategy: c.grouping_strategy.into(), + sign_display: c.sign_display.into(), + } + } +} diff --git a/components/capi/src/lib.rs b/components/capi/src/lib.rs index 99283bb4641..841eafd95f7 100644 --- a/components/capi/src/lib.rs +++ b/components/capi/src/lib.rs @@ -4,6 +4,10 @@ #![allow(clippy::upper_case_acronyms)] +#[macro_use] +mod macros; + +pub mod decimal; pub mod fixed_decimal; pub mod locale; pub mod pluralrules; diff --git a/components/capi/src/macros.rs b/components/capi/src/macros.rs new file mode 100644 index 00000000000..fe5162c1d3a --- /dev/null +++ b/components/capi/src/macros.rs @@ -0,0 +1,49 @@ +macro_rules! c_enum { + ($(#[$docs:meta])* pub c_enum $cname:ident is $rustname:ident { $($variant:ident,)+ } ) => { + #[repr(C)] + $(#[$docs])* + pub enum $cname { + $($variant,)+ + } + + impl From<$rustname> for $cname { + fn from(r: $rustname) -> Self { + match r { + $($rustname::$variant => $cname::$variant,)+ + } + } + } + + impl From<$cname> for $rustname { + fn from(c: $cname) -> Self { + match c { + $($cname::$variant => $rustname::$variant,)+ + } + } + } + }; + ($(#[$docs:meta])* pub c_enum $cname:ident is #[non_exhaustive] $rustname:ident { $($variant:ident,)+ } ) => { + #[repr(C)] + $(#[$docs])* + pub enum $cname { + $($variant,)+ + } + + impl From<$rustname> for $cname { + fn from(r: $rustname) -> Self { + match r { + $($rustname::$variant => $cname::$variant,)+ + _ => unreachable!("Found new variant not reflected in the C API") + } + } + } + + impl From<$cname> for $rustname { + fn from(c: $cname) -> Self { + match c { + $($cname::$variant => $rustname::$variant,)+ + } + } + } + }; +} diff --git a/components/capi/src/pluralrules.rs b/components/capi/src/pluralrules.rs index 6e73c7367c7..2e79df3a80f 100644 --- a/components/capi/src/pluralrules.rs +++ b/components/capi/src/pluralrules.rs @@ -84,22 +84,25 @@ pub struct ICU4XPluralOperands { pub c: usize, } -#[repr(C)] -/// FFI version of [`PluralRuleType`]. See its docs for more details. -pub enum ICU4XPluralRuleType { - Cardinal, - Ordinal, +c_enum! { + /// FFI version of [`PluralRuleType`]. See its docs for more details. + pub c_enum ICU4XPluralRuleType is PluralRuleType { + Cardinal, + Ordinal, + } } #[repr(C)] -/// FFI version of [`PluralCategory`]. See its docs for more details. -pub enum ICU4XPluralCategory { - Zero, - One, - Two, - Few, - Many, - Other, +c_enum! { + /// FFI version of [`PluralCategory`]. See its docs for more details. + pub c_enum ICU4XPluralCategory is PluralCategory { + Zero, + One, + Two, + Few, + Many, + Other, + } } impl From for ICU4XPluralOperands { @@ -127,24 +130,3 @@ impl From for PluralOperands { } } } - -impl From for PluralRuleType { - fn from(other: ICU4XPluralRuleType) -> Self { - match other { - ICU4XPluralRuleType::Cardinal => Self::Cardinal, - ICU4XPluralRuleType::Ordinal => Self::Ordinal, - } - } -} -impl From for ICU4XPluralCategory { - fn from(other: PluralCategory) -> Self { - match other { - PluralCategory::Zero => Self::Zero, - PluralCategory::One => Self::One, - PluralCategory::Two => Self::Two, - PluralCategory::Few => Self::Few, - PluralCategory::Many => Self::Many, - PluralCategory::Other => Self::Other, - } - } -} From e90813bd4e0dfa367df7c17b491c6329d00d9763 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 10:56:08 -0700 Subject: [PATCH 03/25] Add ICU4XCustomWriteable --- components/capi/src/custom_writeable.rs | 94 +++++++++++++++++++++++++ components/capi/src/lib.rs | 1 + 2 files changed, 95 insertions(+) create mode 100644 components/capi/src/custom_writeable.rs diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs new file mode 100644 index 00000000000..8f4aa2c0f56 --- /dev/null +++ b/components/capi/src/custom_writeable.rs @@ -0,0 +1,94 @@ +use std::ffi::c_void; +use std::{fmt, ptr}; + +#[repr(C)] +/// An object that can one can write UTF-8 strings to +/// +/// This allows the C API to write to arbitrary kinds of objects, for example a +/// C++ std::string or a char buffer. +/// +/// [`icu4x_simple_writeable()`] can be used to write to a fixed-size char buffer. +/// +/// # Safety invariants: +/// - `flush()` and `grow()` will be passed `data` and the value should be valid for that +/// `data` may be null, however `flush()` and `grow()` must then be ready to receive it +/// - `buf` must be `cap` bytes long +/// - `grow()` must either return null or a valid buffer of at least the requested buffer size +/// - Rust code must call `ICU4XCustomWriteable::flush()` before releasing to C +pub struct ICU4XCustomWriteable { + /// Pointer to the actual object. While we're writing, we will write + /// directly to `buf` without updating `data`'s state. + data: *mut c_void, + /// The buffer to write to + buf: *mut u8, + /// The current filled size of the buffer + len: usize, + /// The current capacity of the buffer + cap: usize, + /// Called by Rust code when it is done writing, updating `data` + /// with the new length + flush: extern "C" fn(*mut c_void, usize), + /// Called by Rust when it needs more capacity, passing + /// in the requested capacity. Returns the new buffer, with + /// the existing data already copied. + /// + /// Should return `null` if the requested capacity could not be achieved + /// + /// The capacity value will be updated if the actually allocated capacity is larger. + grow: extern "C" fn(*mut c_void, *mut usize) -> *mut u8, +} + +impl ICU4XCustomWriteable { + /// Call this function before releasing the buffer to C + pub fn flush(&mut self) { + (self.flush)(self.data, self.len); + } +} +impl fmt::Write for ICU4XCustomWriteable { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + let mut needed_len = self.len + s.len(); + if needed_len > self.cap { + let newbuf = (self.grow)(self.data, &mut needed_len); + if newbuf.is_null() { + return Err(fmt::Error); + } + self.cap = needed_len; + self.buf = newbuf; + } + let needed_len = self.len + s.len(); + debug_assert!(needed_len <= self.cap); + unsafe { + ptr::copy_nonoverlapping( + s.as_bytes().as_ptr(), + self.buf.offset(self.len as isize), + s.len(), + ); + } + self.len = needed_len; + Ok(()) + } +} + +/// Create an `ICU4XCustomWriteable` that can write to a fixed-length stack allocated `u8` buffer. +/// +/// Once done, this will append a null terminator to the written string. +#[no_mangle] +pub unsafe extern "C" fn icu4x_simple_writeable(buf: *mut u8, len: usize) -> ICU4XCustomWriteable { + extern "C" fn grow(_data: *mut c_void, _cap: *mut usize) -> *mut u8 { + ptr::null_mut() + } + extern "C" fn flush(data: *mut c_void, size: usize) { + unsafe { + let buf = data as *mut u8; + ptr::write(buf.offset(size as isize), 0) + } + } + ICU4XCustomWriteable { + data: buf as *mut c_void, + buf, + len: 0, + cap: len - 1, + flush, + grow, + } +} diff --git a/components/capi/src/lib.rs b/components/capi/src/lib.rs index 841eafd95f7..fc1b502c3eb 100644 --- a/components/capi/src/lib.rs +++ b/components/capi/src/lib.rs @@ -7,6 +7,7 @@ #[macro_use] mod macros; +pub mod custom_writeable; pub mod decimal; pub mod fixed_decimal; pub mod locale; From e8b679388085722dee276dae057b90014b8d5408 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 11:10:05 -0700 Subject: [PATCH 04/25] Add FixedDecimalFormat::format() --- Cargo.lock | 1 + components/capi/Cargo.toml | 1 + components/capi/src/decimal.rs | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index fefa4557686..af763cc4cf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,6 +822,7 @@ dependencies = [ "icu_plurals", "icu_provider", "icu_provider_fs", + "writeable", ] [[package]] diff --git a/components/capi/Cargo.toml b/components/capi/Cargo.toml index 33b188af9cf..673e05e3a9b 100644 --- a/components/capi/Cargo.toml +++ b/components/capi/Cargo.toml @@ -21,3 +21,4 @@ icu_locid = { path = "../locid" } icu_plurals = { path = "../plurals/" } icu_provider = { path = "../provider" } icu_provider_fs = { path = "../provider_fs/" } +writeable = { path = "../../utils/writeable/" } diff --git a/components/capi/src/decimal.rs b/components/capi/src/decimal.rs index 935bc2dc59c..637c473eb5e 100644 --- a/components/capi/src/decimal.rs +++ b/components/capi/src/decimal.rs @@ -2,6 +2,8 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +use crate::custom_writeable::ICU4XCustomWriteable; +use crate::fixed_decimal::ICU4XFixedDecimal; use crate::locale::ICU4XLocale; use crate::provider::ICU4XDataProvider; use icu_decimal::options::{FixedDecimalFormatOptions, GroupingStrategy, SignDisplay}; @@ -52,6 +54,23 @@ pub extern "C" fn icu4x_fixed_decimal_format_create<'d>( } } +#[no_mangle] +/// FFI version of [`FixedDecimalFormat::format()`]. See its docs for more details. +/// +/// Returns `false` when there were errors writing to `write` +pub extern "C" fn icu4x_fixed_decimal_format_format( + fdf: &ICU4XFixedDecimalFormat<'_>, + value: &ICU4XFixedDecimal, + write: &mut ICU4XCustomWriteable, +) -> bool { + use writeable::Writeable; + + let formatted = fdf.format(value); + let result = formatted.write_to(write).is_ok(); + write.flush(); + result +} + #[no_mangle] /// Destructor for [`ICU4XFixedDecimalFormat`] /// From 3de282e68fb02e174bf47ad4fb0587f6a279cc9c Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 11:35:36 -0700 Subject: [PATCH 05/25] Add c headers --- components/capi/include/custom_writeable.h | 23 ++++++++++ components/capi/include/decimal.h | 50 ++++++++++++++++++++++ components/capi/include/fixed_decimal.h | 20 +++++++++ 3 files changed, 93 insertions(+) create mode 100644 components/capi/include/custom_writeable.h create mode 100644 components/capi/include/decimal.h create mode 100644 components/capi/include/fixed_decimal.h diff --git a/components/capi/include/custom_writeable.h b/components/capi/include/custom_writeable.h new file mode 100644 index 00000000000..e77a8778d14 --- /dev/null +++ b/components/capi/include/custom_writeable.h @@ -0,0 +1,23 @@ +// 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 ). + +#ifndef ICU4X_CUSTOM_WRITEABLE_H +#define ICU4X_CUSTOM_WRITEABLE_H + +#include +#include +#include + +typedef struct { + void* data; + char* buf; + size_t len; + size_t cap; + void (*flush)(void*, size_t); + char (*grow)(void*, size_t*); +} ICU4XCustomWriteable; + +ICU4XCustomWriteable icu4x_simple_writeable(char* buf, size_t len); + +#endif // ICU4X_CUSTOM_WRITEABLE_H \ No newline at end of file diff --git a/components/capi/include/decimal.h b/components/capi/include/decimal.h new file mode 100644 index 00000000000..310cfbd8546 --- /dev/null +++ b/components/capi/include/decimal.h @@ -0,0 +1,50 @@ +// 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 ). + +#ifndef ICU4X_DECIMAL_H +#define ICU4X_DECIMAL_H + +#include +#include +#include +#include "provider.h" +#include "locale.h" +#include "fixed_decimal.h" +#include "custom_writeable.h" + +// opaque +typedef struct ICU4XFixedDecimalFormat ICU4XFixedDecimalFormat; + +typedef struct { + ICU4XFixedDecimalFormat* fdf; + bool success; +} ICU4XCreateFixedDecimalFormatResult; + +typedef enum { + ICU4XGroupingStrategy_Auto, + ICU4XGroupingStrategy_Never, + ICU4XGroupingStrategy_Always, + ICU4XGroupingStrategy_Min2, +} ICU4XGroupingStrategy; + +typedef enum { + ICU4XSignDisplay_Auto, + ICU4XSignDisplay_Never, + ICU4XSignDisplay_Always, + ICU4XSignDisplay_ExceptZero, + ICU4XSignDisplay_Negative, +} ICU4XSignDisplay; + +typedef struct { + ICU4XGroupingStrategy grouping_strategy; + ICU4XSignDisplay sign_display; +} ICU4XFixedDecimalFormatOptions; + +ICU4XCreateFixedDecimalFormatResult icu4x_fixed_decimal_format_create(const ICU4XLocale* locale, const ICU4XDataProvider* provider, ICU4XFixedDecimalFormatOptions options); + +bool icu4x_fixed_decimal_format_format(const ICU4XFixedDecimalFormat* fdf, const ICU4XFixedDecimal* value, ICU4XCustomWriteable* write); +void icu4x_fixed_decimal_format_destroy(ICU4XFixedDecimalFormat* fdf); + + +#endif // ICU4X_DECIMAL_H \ No newline at end of file diff --git a/components/capi/include/fixed_decimal.h b/components/capi/include/fixed_decimal.h new file mode 100644 index 00000000000..33a3d2ebfea --- /dev/null +++ b/components/capi/include/fixed_decimal.h @@ -0,0 +1,20 @@ +// 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 ). + +#ifndef ICU4X_FIXED_DECIMAL_H +#define ICU4X_FIXED_DECIMAL_H + +// opaque +typedef struct ICU4XFixedDecimal ICU4XFixedDecimal; + +typedef struct { + ICU4XFixedDecimal* decimal; + bool success; +} ICU4XCreateFixedDecimalResult; + +ICU4XCreateFixedDecimalResult icu4x_fixed_decimal_create(int64_t magnitude, int16_t power); + +void icu4x_fixed_decimal_destroy(ICU4XFixedDecimal* fd); + +#endif // ICU4X_FIXED_DECIMAL_H \ No newline at end of file From 146db26cec70cbda16b2ad5040495309d7d3a098 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 12:06:16 -0700 Subject: [PATCH 06/25] Add fixeddecimal example --- Makefile.toml | 2 + .../capi/examples/fixeddecimal/.gitignore | 1 + .../capi/examples/fixeddecimal/Makefile | 25 ++++++++++ components/capi/examples/fixeddecimal/test.c | 50 +++++++++++++++++++ components/capi/examples/pluralrules/Makefile | 2 +- 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 components/capi/examples/fixeddecimal/.gitignore create mode 100644 components/capi/examples/fixeddecimal/Makefile create mode 100644 components/capi/examples/fixeddecimal/test.c diff --git a/Makefile.toml b/Makefile.toml index 5fafb3a3d27..1f88b9dbd23 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -24,6 +24,8 @@ category = "ICU4X Development" script = ''' cd components/capi/examples/pluralrules; make +cd components/capi/examples/fixeddecimal; +make ''' [tasks.license-header-check] diff --git a/components/capi/examples/fixeddecimal/.gitignore b/components/capi/examples/fixeddecimal/.gitignore new file mode 100644 index 00000000000..f8305e747aa --- /dev/null +++ b/components/capi/examples/fixeddecimal/.gitignore @@ -0,0 +1 @@ +a.out \ No newline at end of file diff --git a/components/capi/examples/fixeddecimal/Makefile b/components/capi/examples/fixeddecimal/Makefile new file mode 100644 index 00000000000..91d3fb79282 --- /dev/null +++ b/components/capi/examples/fixeddecimal/Makefile @@ -0,0 +1,25 @@ +# 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 ). + +.DEFAULT_GOAL := test +.PHONY: build test + +ALL_HEADERS := $(wildcard ../../include/*.h) +ALL_RUST := $(wildcard ../../src/*.rs) + +$(ALL_RUST): + +$(ALL_HEADERS): + + +../../../../target/debug/libicu_capi.a: $(ALL_RUST) + cargo build + +a.out: ../../../../target/debug/libicu_capi.a $(ALL_HEADERS) test.c + gcc test.c ../../../../target/debug/libicu_capi.a -ldl -lpthread -lm -g + +build: a.out + +test: build + ./a.out \ No newline at end of file diff --git a/components/capi/examples/fixeddecimal/test.c b/components/capi/examples/fixeddecimal/test.c new file mode 100644 index 00000000000..ce584b7cbb9 --- /dev/null +++ b/components/capi/examples/fixeddecimal/test.c @@ -0,0 +1,50 @@ +// 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 ). + +#include "../../include/decimal.h" +#include +#include + +const char* path = "../../../../resources/testdata/data/json/"; +int main() { + ICU4XLocale* locale = icu4x_locale_create("bn", 2); + ICU4XCreateDataProviderResult result = icu4x_fs_data_provider_create(path, strlen(path)); + if (!result.success) { + printf("Failed to create FsDataProvider\n"); + return 1; + } + ICU4XDataProvider provider = result.provider; + ICU4XCreateFixedDecimalResult decimal_result = icu4x_fixed_decimal_create(1000007, 0); + if (!decimal_result.success) { + printf("Failed to create FixedDecimal\n"); + return 1; + } + ICU4XFixedDecimal* decimal = decimal_result.decimal; + ICU4XFixedDecimalFormatOptions opts = {ICU4XGroupingStrategy_Auto, ICU4XSignDisplay_Auto}; + + ICU4XCreateFixedDecimalFormatResult fdf_result = icu4x_fixed_decimal_format_create(locale, &provider, opts); + if (!fdf_result.success) { + printf("Failed to create FixedDecimalFormat\n"); + return 1; + } + ICU4XFixedDecimalFormat* fdf = fdf_result.fdf; + char output[40]; + + ICU4XCustomWriteable write = icu4x_simple_writeable(output, 40); + + bool success = icu4x_fixed_decimal_format_format(fdf, decimal, &write); + if (!success) { + printf("Failed to write result of FixedDecimalFormat::format to string.\n"); + return 1; + } + printf("Output is %s\n", output); + + char* expected = "১০,০০,০০৭"; + if (strcmp(output, expected) != 0) { + printf("Output does not match expected output!\n"); + return 1; + } + + return 0; +} diff --git a/components/capi/examples/pluralrules/Makefile b/components/capi/examples/pluralrules/Makefile index c9b10207697..91d3fb79282 100644 --- a/components/capi/examples/pluralrules/Makefile +++ b/components/capi/examples/pluralrules/Makefile @@ -17,7 +17,7 @@ $(ALL_HEADERS): cargo build a.out: ../../../../target/debug/libicu_capi.a $(ALL_HEADERS) test.c - gcc test.c ../../../../target/debug/libicu_capi.a -ldl -lpthread -lm + gcc test.c ../../../../target/debug/libicu_capi.a -ldl -lpthread -lm -g build: a.out From 14a2758ff001ad5041e54907d549c8a4c57468c0 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 14:07:02 -0700 Subject: [PATCH 07/25] more comments on custom_writeable --- components/capi/src/custom_writeable.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index 8f4aa2c0f56..379377e1d41 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -9,6 +9,8 @@ use std::{fmt, ptr}; /// /// [`icu4x_simple_writeable()`] can be used to write to a fixed-size char buffer. /// +/// May be extended in the future to support further invariants +/// /// # Safety invariants: /// - `flush()` and `grow()` will be passed `data` and the value should be valid for that /// `data` may be null, however `flush()` and `grow()` must then be ready to receive it @@ -17,9 +19,10 @@ use std::{fmt, ptr}; /// - Rust code must call `ICU4XCustomWriteable::flush()` before releasing to C pub struct ICU4XCustomWriteable { /// Pointer to the actual object. While we're writing, we will write - /// directly to `buf` without updating `data`'s state. + /// directly to `buf` without updating `data`'s state, this pointer exists so that + /// `grow()` and `flush()` can get access to the full object on the foreign side data: *mut c_void, - /// The buffer to write to + /// The buffer to write directly to buf: *mut u8, /// The current filled size of the buffer len: usize, From 9637209eddabe786a4a77eff704da68d3efc59e4 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 14:47:23 -0700 Subject: [PATCH 08/25] Update components/capi/src/custom_writeable.rs Co-authored-by: Shane F. Carr --- components/capi/src/custom_writeable.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index 379377e1d41..c640a76672d 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -49,16 +49,16 @@ impl ICU4XCustomWriteable { } impl fmt::Write for ICU4XCustomWriteable { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { - let mut needed_len = self.len + s.len(); + let needed_len = self.len + s.len(); if needed_len > self.cap { - let newbuf = (self.grow)(self.data, &mut needed_len); + let mut new_cap = needed_len; + let newbuf = (self.grow)(self.data, &mut new_cap); if newbuf.is_null() { return Err(fmt::Error); } - self.cap = needed_len; + self.cap = new_cap; self.buf = newbuf; } - let needed_len = self.len + s.len(); debug_assert!(needed_len <= self.cap); unsafe { ptr::copy_nonoverlapping( From fbdea4da63533e6551ffdf5ba95e5cb2a7e1cbbf Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 15:12:39 -0700 Subject: [PATCH 09/25] Address review comments --- components/capi/examples/fixeddecimal/test.c | 4 +-- components/capi/include/custom_writeable.h | 2 +- components/capi/include/decimal.h | 2 +- components/capi/src/custom_writeable.rs | 30 ++++++++++++-------- components/capi/src/decimal.rs | 2 +- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/components/capi/examples/fixeddecimal/test.c b/components/capi/examples/fixeddecimal/test.c index ce584b7cbb9..4b6d1378b65 100644 --- a/components/capi/examples/fixeddecimal/test.c +++ b/components/capi/examples/fixeddecimal/test.c @@ -33,14 +33,14 @@ int main() { ICU4XCustomWriteable write = icu4x_simple_writeable(output, 40); - bool success = icu4x_fixed_decimal_format_format(fdf, decimal, &write); + bool success = icu4x_fixed_decimal_format_write(fdf, decimal, &write); if (!success) { printf("Failed to write result of FixedDecimalFormat::format to string.\n"); return 1; } printf("Output is %s\n", output); - char* expected = "১০,০০,০০৭"; + const char* expected = u8"১০,০০,০০৭"; if (strcmp(output, expected) != 0) { printf("Output does not match expected output!\n"); return 1; diff --git a/components/capi/include/custom_writeable.h b/components/capi/include/custom_writeable.h index e77a8778d14..1eca1b0a5bf 100644 --- a/components/capi/include/custom_writeable.h +++ b/components/capi/include/custom_writeable.h @@ -10,7 +10,7 @@ #include typedef struct { - void* data; + void* context; char* buf; size_t len; size_t cap; diff --git a/components/capi/include/decimal.h b/components/capi/include/decimal.h index 310cfbd8546..14d2ea32377 100644 --- a/components/capi/include/decimal.h +++ b/components/capi/include/decimal.h @@ -43,7 +43,7 @@ typedef struct { ICU4XCreateFixedDecimalFormatResult icu4x_fixed_decimal_format_create(const ICU4XLocale* locale, const ICU4XDataProvider* provider, ICU4XFixedDecimalFormatOptions options); -bool icu4x_fixed_decimal_format_format(const ICU4XFixedDecimalFormat* fdf, const ICU4XFixedDecimal* value, ICU4XCustomWriteable* write); +bool icu4x_fixed_decimal_format_write(const ICU4XFixedDecimalFormat* fdf, const ICU4XFixedDecimal* value, ICU4XCustomWriteable* write); void icu4x_fixed_decimal_format_destroy(ICU4XFixedDecimalFormat* fdf); diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index c640a76672d..eea94d0ff42 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -11,24 +11,29 @@ use std::{fmt, ptr}; /// /// May be extended in the future to support further invariants /// +/// ICU4XCustomWriteable will not perform any cleanup on `context` or `buf`, these are logically +/// "borrows" from the FFI side. +/// /// # Safety invariants: -/// - `flush()` and `grow()` will be passed `data` and the value should be valid for that -/// `data` may be null, however `flush()` and `grow()` must then be ready to receive it +/// - `flush()` and `grow()` will be passed `context` and the value should be valid for that +/// `context` may be null, however `flush()` and `grow()` must then be ready to receive it /// - `buf` must be `cap` bytes long /// - `grow()` must either return null or a valid buffer of at least the requested buffer size /// - Rust code must call `ICU4XCustomWriteable::flush()` before releasing to C pub struct ICU4XCustomWriteable { /// Pointer to the actual object. While we're writing, we will write - /// directly to `buf` without updating `data`'s state, this pointer exists so that - /// `grow()` and `flush()` can get access to the full object on the foreign side - data: *mut c_void, + /// directly to `buf` without updating `context`'s state, this pointer exists so that + /// `grow()` and `flush()` can get access to the full object on the foreign side. + /// + /// This can be null if the + context: *mut c_void, /// The buffer to write directly to buf: *mut u8, /// The current filled size of the buffer len: usize, /// The current capacity of the buffer cap: usize, - /// Called by Rust code when it is done writing, updating `data` + /// Called by Rust code when it is done writing, updating `context` /// with the new length flush: extern "C" fn(*mut c_void, usize), /// Called by Rust when it needs more capacity, passing @@ -44,7 +49,7 @@ pub struct ICU4XCustomWriteable { impl ICU4XCustomWriteable { /// Call this function before releasing the buffer to C pub fn flush(&mut self) { - (self.flush)(self.data, self.len); + (self.flush)(self.context, self.len); } } impl fmt::Write for ICU4XCustomWriteable { @@ -52,7 +57,7 @@ impl fmt::Write for ICU4XCustomWriteable { let needed_len = self.len + s.len(); if needed_len > self.cap { let mut new_cap = needed_len; - let newbuf = (self.grow)(self.data, &mut new_cap); + let newbuf = (self.grow)(self.context, &mut new_cap); if newbuf.is_null() { return Err(fmt::Error); } @@ -77,19 +82,20 @@ impl fmt::Write for ICU4XCustomWriteable { /// Once done, this will append a null terminator to the written string. #[no_mangle] pub unsafe extern "C" fn icu4x_simple_writeable(buf: *mut u8, len: usize) -> ICU4XCustomWriteable { - extern "C" fn grow(_data: *mut c_void, _cap: *mut usize) -> *mut u8 { + extern "C" fn grow(_context: *mut c_void, _cap: *mut usize) -> *mut u8 { ptr::null_mut() } - extern "C" fn flush(data: *mut c_void, size: usize) { + extern "C" fn flush(context: *mut c_void, size: usize) { unsafe { - let buf = data as *mut u8; + let buf = context as *mut u8; ptr::write(buf.offset(size as isize), 0) } } ICU4XCustomWriteable { - data: buf as *mut c_void, + context: buf as *mut c_void, buf, len: 0, + // keep an extra byte in our pocket for the null terminator cap: len - 1, flush, grow, diff --git a/components/capi/src/decimal.rs b/components/capi/src/decimal.rs index 637c473eb5e..3973ffe325c 100644 --- a/components/capi/src/decimal.rs +++ b/components/capi/src/decimal.rs @@ -58,7 +58,7 @@ pub extern "C" fn icu4x_fixed_decimal_format_create<'d>( /// FFI version of [`FixedDecimalFormat::format()`]. See its docs for more details. /// /// Returns `false` when there were errors writing to `write` -pub extern "C" fn icu4x_fixed_decimal_format_format( +pub extern "C" fn icu4x_fixed_decimal_format_write( fdf: &ICU4XFixedDecimalFormat<'_>, value: &ICU4XFixedDecimal, write: &mut ICU4XCustomWriteable, From bbea30eaed0c6edf2ebb73730e5d96c490d2d856 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 16:47:55 -0700 Subject: [PATCH 10/25] Update components/capi/src/custom_writeable.rs Co-authored-by: Shane F. Carr --- components/capi/src/custom_writeable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index eea94d0ff42..582b7c380d8 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -27,7 +27,7 @@ pub struct ICU4XCustomWriteable { /// /// This can be null if the context: *mut c_void, - /// The buffer to write directly to + /// The raw string buffer, which will be mutated on the Rust side. buf: *mut u8, /// The current filled size of the buffer len: usize, From 8aade1893f715332248a29e81efa64826b831f1b Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 29 Apr 2021 16:53:28 -0700 Subject: [PATCH 11/25] Writeable docs --- components/capi/src/custom_writeable.rs | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index 582b7c380d8..0b97a90ff60 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -15,17 +15,16 @@ use std::{fmt, ptr}; /// "borrows" from the FFI side. /// /// # Safety invariants: -/// - `flush()` and `grow()` will be passed `context` and the value should be valid for that -/// `context` may be null, however `flush()` and `grow()` must then be ready to receive it +/// - `flush()` and `grow()` will be passed `context` and it should always be safe to do so. +/// `context` may be null, however `flush()` and `grow()` must then be ready to receive it as such. /// - `buf` must be `cap` bytes long /// - `grow()` must either return null or a valid buffer of at least the requested buffer size /// - Rust code must call `ICU4XCustomWriteable::flush()` before releasing to C pub struct ICU4XCustomWriteable { - /// Pointer to the actual object. While we're writing, we will write - /// directly to `buf` without updating `context`'s state, this pointer exists so that - /// `grow()` and `flush()` can get access to the full object on the foreign side. + /// Context pointer passed to `grow()` and `flush()`. May be `null`. /// - /// This can be null if the + /// The pointer may reference structured data on the foreign side, + /// such as C++ std::string, used to reallocate buf. context: *mut c_void, /// The raw string buffer, which will be mutated on the Rust side. buf: *mut u8, @@ -33,16 +32,21 @@ pub struct ICU4XCustomWriteable { len: usize, /// The current capacity of the buffer cap: usize, - /// Called by Rust code when it is done writing, updating `context` - /// with the new length + /// Called by Rust to indicate that there is no more data to write. + /// + /// Arguments: + /// - `context` (`*mut c_void`): The `context` field of this struct + /// - `length` (`usize`): The final length of the string in `buf` flush: extern "C" fn(*mut c_void, usize), - /// Called by Rust when it needs more capacity, passing - /// in the requested capacity. Returns the new buffer, with - /// the existing data already copied. + /// Called by Rust to request more capacity in the buffer. The implementation should allocate a new + /// buffer and copy the contents of the old buffer into the new buffer. /// - /// Should return `null` if the requested capacity could not be achieved + /// Arguments: + /// - `context` (`*mut c_void`): The `context` field of this struct. + /// - `capacity` (`*mut usize`): The requested capacity. Should be updated to reflect + /// the actual capacity if the allocated buffer is larger than was requested. /// - /// The capacity value will be updated if the actually allocated capacity is larger. + /// Returns: the newly allocated buffer, or `null` if allocation failed. grow: extern "C" fn(*mut c_void, *mut usize) -> *mut u8, } From bd79661de804e280448228331d425132042cf116 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Fri, 30 Apr 2021 11:49:47 -0700 Subject: [PATCH 12/25] Improve docs --- components/capi/src/custom_writeable.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index 0b97a90ff60..1521ee724bb 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -7,6 +7,16 @@ use std::{fmt, ptr}; /// This allows the C API to write to arbitrary kinds of objects, for example a /// C++ std::string or a char buffer. /// +/// The way to use this object is to fill out the `buf`, `len`, `cap` fields with +/// appropriate values for the buffer, its current length, and its current capacity, +/// and `flush` and `grow` with appropriate callbacks (using `context` to reference any +/// state they need). This object will be passed by mutable reference to the Rust side, +/// and Rust will write to it, calling `grow()` as necessary. Once done, it will call `flush()` +/// to update any state on `context` (e.g. adding a null terminator, updating the length). +/// The object on the foreign side will be directly usable after this, the foreign side +/// need not perform additional state updates after passing an [`ICU4XCustomWriteable`] to +/// a function. +/// /// [`icu4x_simple_writeable()`] can be used to write to a fixed-size char buffer. /// /// May be extended in the future to support further invariants @@ -85,7 +95,10 @@ impl fmt::Write for ICU4XCustomWriteable { /// /// Once done, this will append a null terminator to the written string. #[no_mangle] -pub unsafe extern "C" fn icu4x_simple_writeable(buf: *mut u8, len: usize) -> ICU4XCustomWriteable { +pub unsafe extern "C" fn icu4x_simple_writeable( + buf: *mut u8, + buf_size: usize, +) -> ICU4XCustomWriteable { extern "C" fn grow(_context: *mut c_void, _cap: *mut usize) -> *mut u8 { ptr::null_mut() } @@ -100,7 +113,7 @@ pub unsafe extern "C" fn icu4x_simple_writeable(buf: *mut u8, len: usize) -> ICU buf, len: 0, // keep an extra byte in our pocket for the null terminator - cap: len - 1, + cap: buf_size - 1, flush, grow, } From 8f200ebf9e52736f1603b3707ffa225a1fb597cc Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Fri, 30 Apr 2021 17:46:04 -0700 Subject: [PATCH 13/25] add license --- components/capi/src/custom_writeable.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index 1521ee724bb..54da921bee0 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -1,3 +1,7 @@ +// 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 std::ffi::c_void; use std::{fmt, ptr}; From 63b00c7d955b87f407926e43f0e18d551393fd72 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 3 May 2021 15:00:48 -0700 Subject: [PATCH 14/25] more review fixes --- components/capi/include/custom_writeable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/capi/include/custom_writeable.h b/components/capi/include/custom_writeable.h index 1eca1b0a5bf..114dda92497 100644 --- a/components/capi/include/custom_writeable.h +++ b/components/capi/include/custom_writeable.h @@ -18,6 +18,6 @@ typedef struct { char (*grow)(void*, size_t*); } ICU4XCustomWriteable; -ICU4XCustomWriteable icu4x_simple_writeable(char* buf, size_t len); +ICU4XCustomWriteable icu4x_simple_writeable(char* buf, size_t buf_size); #endif // ICU4X_CUSTOM_WRITEABLE_H \ No newline at end of file From 5b56dc0e2ddf8100183d852847e80947419570c4 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 3 May 2021 15:09:18 -0700 Subject: [PATCH 15/25] pass ICU4XCustomWriteable to its callbacks --- components/capi/include/custom_writeable.h | 6 +-- components/capi/src/custom_writeable.rs | 44 ++++++++++------------ 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/components/capi/include/custom_writeable.h b/components/capi/include/custom_writeable.h index 114dda92497..769efdb559b 100644 --- a/components/capi/include/custom_writeable.h +++ b/components/capi/include/custom_writeable.h @@ -9,13 +9,13 @@ #include #include -typedef struct { +typedef struct ICU4XCustomWriteable { void* context; char* buf; size_t len; size_t cap; - void (*flush)(void*, size_t); - char (*grow)(void*, size_t*); + void (*flush)(struct ICU4XCustomWriteable*); + char (*grow)(struct ICU4XCustomWriteable*, size_t); } ICU4XCustomWriteable; ICU4XCustomWriteable icu4x_simple_writeable(char* buf, size_t buf_size); diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index 54da921bee0..cff8d7ebc97 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -29,13 +29,14 @@ use std::{fmt, ptr}; /// "borrows" from the FFI side. /// /// # Safety invariants: -/// - `flush()` and `grow()` will be passed `context` and it should always be safe to do so. +/// - `flush()` and `grow()` will be passed `self` including `context` and it should always be safe to do so. /// `context` may be null, however `flush()` and `grow()` must then be ready to receive it as such. /// - `buf` must be `cap` bytes long -/// - `grow()` must either return null or a valid buffer of at least the requested buffer size +/// - `grow()` must either return false or update `buf` and `cap` for a valid buffer +/// - of at least the requested buffer size /// - Rust code must call `ICU4XCustomWriteable::flush()` before releasing to C pub struct ICU4XCustomWriteable { - /// Context pointer passed to `grow()` and `flush()`. May be `null`. + /// Context pointer for additional data needed by `grow()` and `flush()`. May be `null`. /// /// The pointer may reference structured data on the foreign side, /// such as C++ std::string, used to reallocate buf. @@ -49,38 +50,33 @@ pub struct ICU4XCustomWriteable { /// Called by Rust to indicate that there is no more data to write. /// /// Arguments: - /// - `context` (`*mut c_void`): The `context` field of this struct - /// - `length` (`usize`): The final length of the string in `buf` - flush: extern "C" fn(*mut c_void, usize), + /// - `self` (`*mut ICU4XCustomWriteable`): This `ICU4XCustomWriteable` + flush: extern "C" fn(*mut ICU4XCustomWriteable), /// Called by Rust to request more capacity in the buffer. The implementation should allocate a new - /// buffer and copy the contents of the old buffer into the new buffer. + /// buffer and copy the contents of the old buffer into the new buffer, updating `self.buf` and `self.cap` /// /// Arguments: - /// - `context` (`*mut c_void`): The `context` field of this struct. - /// - `capacity` (`*mut usize`): The requested capacity. Should be updated to reflect - /// the actual capacity if the allocated buffer is larger than was requested. + /// - `self` (`*mut ICU4XCustomWriteable`): This `ICU4XCustomWriteable` + /// - `capacity` (`usize`): The requested capacity. /// - /// Returns: the newly allocated buffer, or `null` if allocation failed. - grow: extern "C" fn(*mut c_void, *mut usize) -> *mut u8, + /// Returns: `true` if the allocation succeeded. Should not update any state if it failed. + grow: extern "C" fn(*mut ICU4XCustomWriteable, usize) -> bool, } impl ICU4XCustomWriteable { /// Call this function before releasing the buffer to C pub fn flush(&mut self) { - (self.flush)(self.context, self.len); + (self.flush)(self); } } impl fmt::Write for ICU4XCustomWriteable { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { let needed_len = self.len + s.len(); if needed_len > self.cap { - let mut new_cap = needed_len; - let newbuf = (self.grow)(self.context, &mut new_cap); - if newbuf.is_null() { + let success = (self.grow)(self, needed_len); + if !success { return Err(fmt::Error); } - self.cap = new_cap; - self.buf = newbuf; } debug_assert!(needed_len <= self.cap); unsafe { @@ -103,17 +99,17 @@ pub unsafe extern "C" fn icu4x_simple_writeable( buf: *mut u8, buf_size: usize, ) -> ICU4XCustomWriteable { - extern "C" fn grow(_context: *mut c_void, _cap: *mut usize) -> *mut u8 { - ptr::null_mut() + extern "C" fn grow(_this: *mut ICU4XCustomWriteable, _cap: usize) -> bool { + false } - extern "C" fn flush(context: *mut c_void, size: usize) { + extern "C" fn flush(this: *mut ICU4XCustomWriteable) { unsafe { - let buf = context as *mut u8; - ptr::write(buf.offset(size as isize), 0) + let buf = (*this).buf; + ptr::write(buf.offset((*this).len as isize), 0) } } ICU4XCustomWriteable { - context: buf as *mut c_void, + context: ptr::null_mut(), buf, len: 0, // keep an extra byte in our pocket for the null terminator From f2fa2a8963a43433f2adc485b3a85c1b5a9ea7ce Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 3 May 2021 17:18:53 -0700 Subject: [PATCH 16/25] Add FixedDecimal::multiply_pow10() --- components/capi/examples/fixeddecimal/test.c | 29 +++++++++++--- components/capi/include/fixed_decimal.h | 8 +--- components/capi/src/fixed_decimal.rs | 41 +++++++------------- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/components/capi/examples/fixeddecimal/test.c b/components/capi/examples/fixeddecimal/test.c index 4b6d1378b65..f75d6447c1c 100644 --- a/components/capi/examples/fixeddecimal/test.c +++ b/components/capi/examples/fixeddecimal/test.c @@ -15,12 +15,8 @@ int main() { return 1; } ICU4XDataProvider provider = result.provider; - ICU4XCreateFixedDecimalResult decimal_result = icu4x_fixed_decimal_create(1000007, 0); - if (!decimal_result.success) { - printf("Failed to create FixedDecimal\n"); - return 1; - } - ICU4XFixedDecimal* decimal = decimal_result.decimal; + ICU4XFixedDecimal* decimal = icu4x_fixed_decimal_create(1000007); + ICU4XFixedDecimalFormatOptions opts = {ICU4XGroupingStrategy_Auto, ICU4XSignDisplay_Auto}; ICU4XCreateFixedDecimalFormatResult fdf_result = icu4x_fixed_decimal_format_create(locale, &provider, opts); @@ -46,5 +42,26 @@ int main() { return 1; } + success = icu4x_fixed_decimal_multiply_pow10(decimal, 2); + if (!success) { + printf("Failed to multiply FixedDecimal\n"); + return 1; + } + + write = icu4x_simple_writeable(output, 40); + + success = icu4x_fixed_decimal_format_write(fdf, decimal, &write); + if (!success) { + printf("Failed to write result of FixedDecimalFormat::format to string.\n"); + return 1; + } + printf("Output x100 is %s\n", output); + + expected = u8"১০,০০,০০,৭০০"; + if (strcmp(output, expected) != 0) { + printf("Output does not match expected output!\n"); + return 1; + } + return 0; } diff --git a/components/capi/include/fixed_decimal.h b/components/capi/include/fixed_decimal.h index 33a3d2ebfea..11a45bf6874 100644 --- a/components/capi/include/fixed_decimal.h +++ b/components/capi/include/fixed_decimal.h @@ -8,12 +8,8 @@ // opaque typedef struct ICU4XFixedDecimal ICU4XFixedDecimal; -typedef struct { - ICU4XFixedDecimal* decimal; - bool success; -} ICU4XCreateFixedDecimalResult; - -ICU4XCreateFixedDecimalResult icu4x_fixed_decimal_create(int64_t magnitude, int16_t power); +ICU4XFixedDecimal* icu4x_fixed_decimal_create(int64_t magnitude); +bool icu4x_fixed_decimal_multiply_pow10(ICU4XFixedDecimal* fd, int16_t power); void icu4x_fixed_decimal_destroy(ICU4XFixedDecimal* fd); diff --git a/components/capi/src/fixed_decimal.rs b/components/capi/src/fixed_decimal.rs index 0bb69f74eb6..0d65e57c0ee 100644 --- a/components/capi/src/fixed_decimal.rs +++ b/components/capi/src/fixed_decimal.rs @@ -10,41 +10,26 @@ use std::ptr; /// Can be obtained via [`icu4x_fixed_decimal_create()`] and destroyed via [`icu4x_fixed_decimal_destroy()`] pub type ICU4XFixedDecimal = FixedDecimal; -#[repr(C)] -/// This is the result returned by [`icu4x_plural_rules_create()`] -pub struct ICU4XCreateFixedDecimalResult { - /// Will be null if `success` is [`false`] - pub decimal: *mut ICU4XFixedDecimal, - /// Currently just a boolean, but we might add a proper error enum as necessary - pub success: bool, -} - #[no_mangle] /// FFI version of [`FixedDecimal`]'s constructors. This constructs a [`FixedDecimal`] of the provided -/// `magnitude` and then multiplies it by `10 ^ pow10`. -/// -/// # Safety -/// - Only access `decimal` in the result if `success` is [`true`]. +/// `magnitude`. // // We can add additional constructors from strings, floats, etc as the need arises -pub extern "C" fn icu4x_fixed_decimal_create( - magnitude: i64, - pow10: i16, -) -> ICU4XCreateFixedDecimalResult { +pub extern "C" fn icu4x_fixed_decimal_create(magnitude: i64) -> *mut ICU4XFixedDecimal { let fd = FixedDecimal::from(magnitude); - if let Ok(multiplied) = fd.multiplied_pow10(pow10) { - ICU4XCreateFixedDecimalResult { - decimal: Box::into_raw(Box::new(multiplied)), - success: true, - } - } else { - ICU4XCreateFixedDecimalResult { - decimal: ptr::null_mut(), - success: false, - } - } + Box::into_raw(Box::new(fd)) } +#[no_mangle] +/// FFI version of [`FixedDecimal::multiply_pow10()`]. See its docs for more details.ICU4XFixedDecimal +/// +/// Returns `true` if the multiplication was successful. +pub extern "C" fn icu4x_fixed_decimal_multiply_pow10( + fd: *mut ICU4XFixedDecimal, + power: i16, +) -> bool { + unsafe { (&mut *fd).multiply_pow10(power).is_ok() } +} #[no_mangle] /// Destructor for [`ICU4XFixedDecimal`] /// From c1536bb5856e3bf250e3637556a1cf8bff5dbc6c Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 3 May 2021 17:20:37 -0700 Subject: [PATCH 17/25] Update components/capi/src/custom_writeable.rs Co-authored-by: Shane F. Carr --- components/capi/src/custom_writeable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index cff8d7ebc97..e570a96d023 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -33,7 +33,7 @@ use std::{fmt, ptr}; /// `context` may be null, however `flush()` and `grow()` must then be ready to receive it as such. /// - `buf` must be `cap` bytes long /// - `grow()` must either return false or update `buf` and `cap` for a valid buffer -/// - of at least the requested buffer size +/// of at least the requested buffer size /// - Rust code must call `ICU4XCustomWriteable::flush()` before releasing to C pub struct ICU4XCustomWriteable { /// Context pointer for additional data needed by `grow()` and `flush()`. May be `null`. From c5ac2c573cf649552bb4256da3ad5b373667b12c Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 3 May 2021 17:22:23 -0700 Subject: [PATCH 18/25] More review fixes --- components/capi/src/custom_writeable.rs | 1 + components/capi/src/fixed_decimal.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index e570a96d023..b6bef68bf86 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -104,6 +104,7 @@ pub unsafe extern "C" fn icu4x_simple_writeable( } extern "C" fn flush(this: *mut ICU4XCustomWriteable) { unsafe { + debug_assert!((*this).len <= (*this).cap); let buf = (*this).buf; ptr::write(buf.offset((*this).len as isize), 0) } diff --git a/components/capi/src/fixed_decimal.rs b/components/capi/src/fixed_decimal.rs index 0d65e57c0ee..9b9c0e5bc86 100644 --- a/components/capi/src/fixed_decimal.rs +++ b/components/capi/src/fixed_decimal.rs @@ -3,7 +3,6 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use fixed_decimal::FixedDecimal; -use std::ptr; /// Opaque type for use behind a pointer, is [`FixedDecimal`] /// From 275497c404b3cdf907fe65819d5bf0b1225fc34f Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 3 May 2021 22:40:26 -0700 Subject: [PATCH 19/25] add license --- components/capi/src/macros.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/capi/src/macros.rs b/components/capi/src/macros.rs index fe5162c1d3a..5438e8e8f51 100644 --- a/components/capi/src/macros.rs +++ b/components/capi/src/macros.rs @@ -1,3 +1,7 @@ +// 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 ). + macro_rules! c_enum { ($(#[$docs:meta])* pub c_enum $cname:ident is $rustname:ident { $($variant:ident,)+ } ) => { #[repr(C)] From fa7967cc8e1240ece720dccff1e12e28f54e0e2b Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 3 May 2021 22:44:25 -0700 Subject: [PATCH 20/25] Lint fixes --- components/capi/src/custom_writeable.rs | 12 ++++++------ components/capi/src/fixed_decimal.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index b6bef68bf86..48785016c0d 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -80,11 +80,7 @@ impl fmt::Write for ICU4XCustomWriteable { } debug_assert!(needed_len <= self.cap); unsafe { - ptr::copy_nonoverlapping( - s.as_bytes().as_ptr(), - self.buf.offset(self.len as isize), - s.len(), - ); + ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf.add(self.len), s.len()); } self.len = needed_len; Ok(()) @@ -94,6 +90,10 @@ impl fmt::Write for ICU4XCustomWriteable { /// Create an `ICU4XCustomWriteable` that can write to a fixed-length stack allocated `u8` buffer. /// /// Once done, this will append a null terminator to the written string. +/// +/// # Safety +/// +/// - `buf` must be a valid pointer to a region of memory that can hold at `buf_size` bytes #[no_mangle] pub unsafe extern "C" fn icu4x_simple_writeable( buf: *mut u8, @@ -106,7 +106,7 @@ pub unsafe extern "C" fn icu4x_simple_writeable( unsafe { debug_assert!((*this).len <= (*this).cap); let buf = (*this).buf; - ptr::write(buf.offset((*this).len as isize), 0) + ptr::write(buf.add((*this).len), 0) } } ICU4XCustomWriteable { diff --git a/components/capi/src/fixed_decimal.rs b/components/capi/src/fixed_decimal.rs index 9b9c0e5bc86..806782a150d 100644 --- a/components/capi/src/fixed_decimal.rs +++ b/components/capi/src/fixed_decimal.rs @@ -23,11 +23,15 @@ pub extern "C" fn icu4x_fixed_decimal_create(magnitude: i64) -> *mut ICU4XFixedD /// FFI version of [`FixedDecimal::multiply_pow10()`]. See its docs for more details.ICU4XFixedDecimal /// /// Returns `true` if the multiplication was successful. -pub extern "C" fn icu4x_fixed_decimal_multiply_pow10( +/// +/// # Safety +/// +/// - `fd` must be a valid pointer to an [`ICU4XFixedDecimal`] +pub unsafe extern "C" fn icu4x_fixed_decimal_multiply_pow10( fd: *mut ICU4XFixedDecimal, power: i16, ) -> bool { - unsafe { (&mut *fd).multiply_pow10(power).is_ok() } + (&mut *fd).multiply_pow10(power).is_ok() } #[no_mangle] /// Destructor for [`ICU4XFixedDecimal`] From e933349c9a170553dc3f75d76bebfe1b9d81d097 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 4 May 2021 13:36:14 -0700 Subject: [PATCH 21/25] Use references in FFI function --- components/capi/src/fixed_decimal.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/components/capi/src/fixed_decimal.rs b/components/capi/src/fixed_decimal.rs index 806782a150d..b33842fa338 100644 --- a/components/capi/src/fixed_decimal.rs +++ b/components/capi/src/fixed_decimal.rs @@ -23,15 +23,11 @@ pub extern "C" fn icu4x_fixed_decimal_create(magnitude: i64) -> *mut ICU4XFixedD /// FFI version of [`FixedDecimal::multiply_pow10()`]. See its docs for more details.ICU4XFixedDecimal /// /// Returns `true` if the multiplication was successful. -/// -/// # Safety -/// -/// - `fd` must be a valid pointer to an [`ICU4XFixedDecimal`] -pub unsafe extern "C" fn icu4x_fixed_decimal_multiply_pow10( - fd: *mut ICU4XFixedDecimal, +pub extern "C" fn icu4x_fixed_decimal_multiply_pow10( + fd: &mut ICU4XFixedDecimal, power: i16, ) -> bool { - (&mut *fd).multiply_pow10(power).is_ok() + fd.multiply_pow10(power).is_ok() } #[no_mangle] /// Destructor for [`ICU4XFixedDecimal`] From 7247566cc4e17a6c0bebea4c7f356957236f281f Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 4 May 2021 14:29:43 -0700 Subject: [PATCH 22/25] ICU4XCustomWriteable -> ICU4XWriteable --- components/capi/examples/fixeddecimal/test.c | 2 +- components/capi/include/custom_writeable.h | 10 +++---- components/capi/include/decimal.h | 2 +- components/capi/src/custom_writeable.rs | 30 ++++++++++---------- components/capi/src/decimal.rs | 4 +-- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/components/capi/examples/fixeddecimal/test.c b/components/capi/examples/fixeddecimal/test.c index f75d6447c1c..9a8edbeb2e5 100644 --- a/components/capi/examples/fixeddecimal/test.c +++ b/components/capi/examples/fixeddecimal/test.c @@ -27,7 +27,7 @@ int main() { ICU4XFixedDecimalFormat* fdf = fdf_result.fdf; char output[40]; - ICU4XCustomWriteable write = icu4x_simple_writeable(output, 40); + ICU4XWriteable write = icu4x_simple_writeable(output, 40); bool success = icu4x_fixed_decimal_format_write(fdf, decimal, &write); if (!success) { diff --git a/components/capi/include/custom_writeable.h b/components/capi/include/custom_writeable.h index 769efdb559b..a7dc98c1a73 100644 --- a/components/capi/include/custom_writeable.h +++ b/components/capi/include/custom_writeable.h @@ -9,15 +9,15 @@ #include #include -typedef struct ICU4XCustomWriteable { +typedef struct ICU4XWriteable { void* context; char* buf; size_t len; size_t cap; - void (*flush)(struct ICU4XCustomWriteable*); - char (*grow)(struct ICU4XCustomWriteable*, size_t); -} ICU4XCustomWriteable; + void (*flush)(struct ICU4XWriteable*); + char (*grow)(struct ICU4XWriteable*, size_t); +} ICU4XWriteable; -ICU4XCustomWriteable icu4x_simple_writeable(char* buf, size_t buf_size); +ICU4XWriteable icu4x_simple_writeable(char* buf, size_t buf_size); #endif // ICU4X_CUSTOM_WRITEABLE_H \ No newline at end of file diff --git a/components/capi/include/decimal.h b/components/capi/include/decimal.h index 14d2ea32377..a0631901b6a 100644 --- a/components/capi/include/decimal.h +++ b/components/capi/include/decimal.h @@ -43,7 +43,7 @@ typedef struct { ICU4XCreateFixedDecimalFormatResult icu4x_fixed_decimal_format_create(const ICU4XLocale* locale, const ICU4XDataProvider* provider, ICU4XFixedDecimalFormatOptions options); -bool icu4x_fixed_decimal_format_write(const ICU4XFixedDecimalFormat* fdf, const ICU4XFixedDecimal* value, ICU4XCustomWriteable* write); +bool icu4x_fixed_decimal_format_write(const ICU4XFixedDecimalFormat* fdf, const ICU4XFixedDecimal* value, ICU4XWriteable* write); void icu4x_fixed_decimal_format_destroy(ICU4XFixedDecimalFormat* fdf); diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index 48785016c0d..d8d09d2a0fb 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -18,14 +18,14 @@ use std::{fmt, ptr}; /// and Rust will write to it, calling `grow()` as necessary. Once done, it will call `flush()` /// to update any state on `context` (e.g. adding a null terminator, updating the length). /// The object on the foreign side will be directly usable after this, the foreign side -/// need not perform additional state updates after passing an [`ICU4XCustomWriteable`] to +/// need not perform additional state updates after passing an [`ICU4XWriteable`] to /// a function. /// /// [`icu4x_simple_writeable()`] can be used to write to a fixed-size char buffer. /// /// May be extended in the future to support further invariants /// -/// ICU4XCustomWriteable will not perform any cleanup on `context` or `buf`, these are logically +/// ICU4XWriteable will not perform any cleanup on `context` or `buf`, these are logically /// "borrows" from the FFI side. /// /// # Safety invariants: @@ -34,8 +34,8 @@ use std::{fmt, ptr}; /// - `buf` must be `cap` bytes long /// - `grow()` must either return false or update `buf` and `cap` for a valid buffer /// of at least the requested buffer size -/// - Rust code must call `ICU4XCustomWriteable::flush()` before releasing to C -pub struct ICU4XCustomWriteable { +/// - Rust code must call `ICU4XWriteable::flush()` before releasing to C +pub struct ICU4XWriteable { /// Context pointer for additional data needed by `grow()` and `flush()`. May be `null`. /// /// The pointer may reference structured data on the foreign side, @@ -50,26 +50,26 @@ pub struct ICU4XCustomWriteable { /// Called by Rust to indicate that there is no more data to write. /// /// Arguments: - /// - `self` (`*mut ICU4XCustomWriteable`): This `ICU4XCustomWriteable` - flush: extern "C" fn(*mut ICU4XCustomWriteable), + /// - `self` (`*mut ICU4XWriteable`): This `ICU4XWriteable` + flush: extern "C" fn(*mut ICU4XWriteable), /// Called by Rust to request more capacity in the buffer. The implementation should allocate a new /// buffer and copy the contents of the old buffer into the new buffer, updating `self.buf` and `self.cap` /// /// Arguments: - /// - `self` (`*mut ICU4XCustomWriteable`): This `ICU4XCustomWriteable` + /// - `self` (`*mut ICU4XWriteable`): This `ICU4XWriteable` /// - `capacity` (`usize`): The requested capacity. /// /// Returns: `true` if the allocation succeeded. Should not update any state if it failed. - grow: extern "C" fn(*mut ICU4XCustomWriteable, usize) -> bool, + grow: extern "C" fn(*mut ICU4XWriteable, usize) -> bool, } -impl ICU4XCustomWriteable { +impl ICU4XWriteable { /// Call this function before releasing the buffer to C pub fn flush(&mut self) { (self.flush)(self); } } -impl fmt::Write for ICU4XCustomWriteable { +impl fmt::Write for ICU4XWriteable { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { let needed_len = self.len + s.len(); if needed_len > self.cap { @@ -87,7 +87,7 @@ impl fmt::Write for ICU4XCustomWriteable { } } -/// Create an `ICU4XCustomWriteable` that can write to a fixed-length stack allocated `u8` buffer. +/// Create an `ICU4XWriteable` that can write to a fixed-length stack allocated `u8` buffer. /// /// Once done, this will append a null terminator to the written string. /// @@ -98,18 +98,18 @@ impl fmt::Write for ICU4XCustomWriteable { pub unsafe extern "C" fn icu4x_simple_writeable( buf: *mut u8, buf_size: usize, -) -> ICU4XCustomWriteable { - extern "C" fn grow(_this: *mut ICU4XCustomWriteable, _cap: usize) -> bool { +) -> ICU4XWriteable { + extern "C" fn grow(_this: *mut ICU4XWriteable, _cap: usize) -> bool { false } - extern "C" fn flush(this: *mut ICU4XCustomWriteable) { + extern "C" fn flush(this: *mut ICU4XWriteable) { unsafe { debug_assert!((*this).len <= (*this).cap); let buf = (*this).buf; ptr::write(buf.add((*this).len), 0) } } - ICU4XCustomWriteable { + ICU4XWriteable { context: ptr::null_mut(), buf, len: 0, diff --git a/components/capi/src/decimal.rs b/components/capi/src/decimal.rs index 3973ffe325c..60e3aefcff1 100644 --- a/components/capi/src/decimal.rs +++ b/components/capi/src/decimal.rs @@ -2,7 +2,7 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use crate::custom_writeable::ICU4XCustomWriteable; +use crate::custom_writeable::ICU4XWriteable; use crate::fixed_decimal::ICU4XFixedDecimal; use crate::locale::ICU4XLocale; use crate::provider::ICU4XDataProvider; @@ -61,7 +61,7 @@ pub extern "C" fn icu4x_fixed_decimal_format_create<'d>( pub extern "C" fn icu4x_fixed_decimal_format_write( fdf: &ICU4XFixedDecimalFormat<'_>, value: &ICU4XFixedDecimal, - write: &mut ICU4XCustomWriteable, + write: &mut ICU4XWriteable, ) -> bool { use writeable::Writeable; From 054ad82a70fe9bfb765c3251b2727b8cd914aa1c Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 4 May 2021 14:33:00 -0700 Subject: [PATCH 23/25] Add negate() --- components/capi/examples/fixeddecimal/test.c | 4 +++- components/capi/include/fixed_decimal.h | 1 + components/capi/src/fixed_decimal.rs | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/components/capi/examples/fixeddecimal/test.c b/components/capi/examples/fixeddecimal/test.c index 9a8edbeb2e5..3184233fd39 100644 --- a/components/capi/examples/fixeddecimal/test.c +++ b/components/capi/examples/fixeddecimal/test.c @@ -48,6 +48,8 @@ int main() { return 1; } + icu4x_fixed_decimal_negate(decimal); + write = icu4x_simple_writeable(output, 40); success = icu4x_fixed_decimal_format_write(fdf, decimal, &write); @@ -57,7 +59,7 @@ int main() { } printf("Output x100 is %s\n", output); - expected = u8"১০,০০,০০,৭০০"; + expected = u8"-১০,০০,০০,৭০০"; if (strcmp(output, expected) != 0) { printf("Output does not match expected output!\n"); return 1; diff --git a/components/capi/include/fixed_decimal.h b/components/capi/include/fixed_decimal.h index 11a45bf6874..2dc980b715c 100644 --- a/components/capi/include/fixed_decimal.h +++ b/components/capi/include/fixed_decimal.h @@ -10,6 +10,7 @@ typedef struct ICU4XFixedDecimal ICU4XFixedDecimal; ICU4XFixedDecimal* icu4x_fixed_decimal_create(int64_t magnitude); bool icu4x_fixed_decimal_multiply_pow10(ICU4XFixedDecimal* fd, int16_t power); +void icu4x_fixed_decimal_negate(ICU4XFixedDecimal* fd); void icu4x_fixed_decimal_destroy(ICU4XFixedDecimal* fd); diff --git a/components/capi/src/fixed_decimal.rs b/components/capi/src/fixed_decimal.rs index b33842fa338..7be64d3764f 100644 --- a/components/capi/src/fixed_decimal.rs +++ b/components/capi/src/fixed_decimal.rs @@ -29,6 +29,13 @@ pub extern "C" fn icu4x_fixed_decimal_multiply_pow10( ) -> bool { fd.multiply_pow10(power).is_ok() } + +#[no_mangle] +/// FFI version of [`FixedDecimal::negate()`]. See its docs for more details.ICU4XFixedDecimal +pub extern "C" fn icu4x_fixed_decimal_negate( + fd: &mut ICU4XFixedDecimal) { + fd.negate() +} #[no_mangle] /// Destructor for [`ICU4XFixedDecimal`] /// From ec9e250d80d7c4052984052d954f59c74764eec1 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 4 May 2021 14:40:29 -0700 Subject: [PATCH 24/25] Fix label in test --- components/capi/examples/fixeddecimal/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/capi/examples/fixeddecimal/test.c b/components/capi/examples/fixeddecimal/test.c index 3184233fd39..6198abe31a6 100644 --- a/components/capi/examples/fixeddecimal/test.c +++ b/components/capi/examples/fixeddecimal/test.c @@ -57,7 +57,7 @@ int main() { printf("Failed to write result of FixedDecimalFormat::format to string.\n"); return 1; } - printf("Output x100 is %s\n", output); + printf("Output x100 and negated is %s\n", output); expected = u8"-১০,০০,০০,৭০০"; if (strcmp(output, expected) != 0) { From 425ec0f1e96e2be5d5f1ce4bc746671aa5d1e27e Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 4 May 2021 14:55:28 -0700 Subject: [PATCH 25/25] fmt --- components/capi/src/custom_writeable.rs | 5 +---- components/capi/src/fixed_decimal.rs | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/capi/src/custom_writeable.rs b/components/capi/src/custom_writeable.rs index d8d09d2a0fb..58fa6b8b7ea 100644 --- a/components/capi/src/custom_writeable.rs +++ b/components/capi/src/custom_writeable.rs @@ -95,10 +95,7 @@ impl fmt::Write for ICU4XWriteable { /// /// - `buf` must be a valid pointer to a region of memory that can hold at `buf_size` bytes #[no_mangle] -pub unsafe extern "C" fn icu4x_simple_writeable( - buf: *mut u8, - buf_size: usize, -) -> ICU4XWriteable { +pub unsafe extern "C" fn icu4x_simple_writeable(buf: *mut u8, buf_size: usize) -> ICU4XWriteable { extern "C" fn grow(_this: *mut ICU4XWriteable, _cap: usize) -> bool { false } diff --git a/components/capi/src/fixed_decimal.rs b/components/capi/src/fixed_decimal.rs index 7be64d3764f..6c1a4fd26a1 100644 --- a/components/capi/src/fixed_decimal.rs +++ b/components/capi/src/fixed_decimal.rs @@ -32,8 +32,7 @@ pub extern "C" fn icu4x_fixed_decimal_multiply_pow10( #[no_mangle] /// FFI version of [`FixedDecimal::negate()`]. See its docs for more details.ICU4XFixedDecimal -pub extern "C" fn icu4x_fixed_decimal_negate( - fd: &mut ICU4XFixedDecimal) { +pub extern "C" fn icu4x_fixed_decimal_negate(fd: &mut ICU4XFixedDecimal) { fd.negate() } #[no_mangle]