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

Add FFI for FixedDecimalFormat #680

Merged
merged 25 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ category = "ICU4X Development"
script = '''
cd components/capi/examples/pluralrules;
make
cd components/capi/examples/fixeddecimal;
make
'''

[tasks.license-header-check]
Expand Down
3 changes: 3 additions & 0 deletions components/capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ crate-type = ["staticlib", "rlib"]
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" }
icu_provider_fs = { path = "../provider_fs/" }
writeable = { path = "../../utils/writeable/" }
1 change: 1 addition & 0 deletions components/capi/examples/fixeddecimal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a.out
25 changes: 25 additions & 0 deletions components/capi/examples/fixeddecimal/Makefile
Original file line number Diff line number Diff line change
@@ -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
69 changes: 69 additions & 0 deletions components/capi/examples/fixeddecimal/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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 <string.h>
#include <stdio.h>

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;
ICU4XFixedDecimal* decimal = icu4x_fixed_decimal_create(1000007);

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];

ICU4XWriteable write = icu4x_simple_writeable(output, 40);

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);

const char* expected = u8"১০,০০,০০৭";
if (strcmp(output, expected) != 0) {
printf("Output does not match expected output!\n");
return 1;
}

success = icu4x_fixed_decimal_multiply_pow10(decimal, 2);
if (!success) {
printf("Failed to multiply FixedDecimal\n");
return 1;
}

icu4x_fixed_decimal_negate(decimal);

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 and negated is %s\n", output);

expected = u8"-১০,০০,০০,৭০০";
if (strcmp(output, expected) != 0) {
printf("Output does not match expected output!\n");
return 1;
}

return 0;
}
2 changes: 1 addition & 1 deletion components/capi/examples/pluralrules/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 23 additions & 0 deletions components/capi/include/custom_writeable.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#include <stddef.h>
#include <stdbool.h>

typedef struct ICU4XWriteable {
void* context;
char* buf;
size_t len;
size_t cap;
void (*flush)(struct ICU4XWriteable*);
char (*grow)(struct ICU4XWriteable*, size_t);
} ICU4XWriteable;

ICU4XWriteable icu4x_simple_writeable(char* buf, size_t buf_size);

#endif // ICU4X_CUSTOM_WRITEABLE_H
50 changes: 50 additions & 0 deletions components/capi/include/decimal.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#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_write(const ICU4XFixedDecimalFormat* fdf, const ICU4XFixedDecimal* value, ICU4XWriteable* write);
void icu4x_fixed_decimal_format_destroy(ICU4XFixedDecimalFormat* fdf);


#endif // ICU4X_DECIMAL_H
17 changes: 17 additions & 0 deletions components/capi/include/fixed_decimal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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;

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);

#endif // ICU4X_FIXED_DECIMAL_H
118 changes: 118 additions & 0 deletions components/capi/src/custom_writeable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// 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};

#[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.
///
/// 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 [`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
///
/// ICU4XWriteable 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 `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 false or update `buf` and `cap` for a valid buffer
/// of at least the requested buffer size
/// - 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,
/// such as C++ std::string, used to reallocate buf.
context: *mut c_void,
Manishearth marked this conversation as resolved.
Show resolved Hide resolved
/// The raw string buffer, which will be mutated on the Rust side.
buf: *mut u8,
/// The current filled size of the buffer
len: usize,
/// The current capacity of the buffer
cap: usize,
/// Called by Rust to indicate that there is no more data to write.
///
/// Arguments:
/// - `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 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 ICU4XWriteable, usize) -> bool,
}

impl ICU4XWriteable {
/// Call this function before releasing the buffer to C
pub fn flush(&mut self) {
(self.flush)(self);
}
}
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 {
let success = (self.grow)(self, needed_len);
if !success {
return Err(fmt::Error);
}
}
debug_assert!(needed_len <= self.cap);
unsafe {
ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf.add(self.len), s.len());
}
self.len = needed_len;
Ok(())
}
}

/// 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.
///
/// # 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, buf_size: usize) -> ICU4XWriteable {
extern "C" fn grow(_this: *mut ICU4XWriteable, _cap: usize) -> bool {
false
}
extern "C" fn flush(this: *mut ICU4XWriteable) {
unsafe {
Manishearth marked this conversation as resolved.
Show resolved Hide resolved
debug_assert!((*this).len <= (*this).cap);
let buf = (*this).buf;
ptr::write(buf.add((*this).len), 0)
}
}
ICU4XWriteable {
context: ptr::null_mut(),
buf,
len: 0,
// keep an extra byte in our pocket for the null terminator
cap: buf_size - 1,
flush,
grow,
}
}
Loading