Implement 'monetary.h', including initial structure and strfmon() function
This commit is contained in:
Generated
+14
-14
@@ -4,9 +4,9 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
@@ -91,9 +91,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.14"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -184,9 +184,9 @@ version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
@@ -263,9 +263,9 @@ version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -464,9 +464,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.77"
|
||||
version = "2.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -481,15 +481,15 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ pub mod libgen;
|
||||
pub mod limits;
|
||||
pub mod locale;
|
||||
// math.h implemented in C
|
||||
// TODO: monetary.h
|
||||
pub mod monetary;
|
||||
// TODO: mqueue.h
|
||||
// TODO: ndbm.h
|
||||
pub mod net_if;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
sys_includes = ["stddef.h", "stdint.h", "features.h"]
|
||||
include_guard = "_RELIBC_MONETARY_H"
|
||||
language = "C"
|
||||
style = "tag"
|
||||
no_includes = true
|
||||
cpp_compat = true
|
||||
|
||||
[enum]
|
||||
prefix_with_name = true
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
|
||||
/// monetary.h implementation for Redox, following the POSIX standard.
|
||||
/// Following https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/monetary.h.html
|
||||
///
|
||||
/// We should provide a strfmon() implementation that formats a monetary value.
|
||||
/// according to the current locale (TODO).
|
||||
use alloc::string::{String, ToString};
|
||||
use core::{ffi::CStr, ptr, slice, str};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod strfmon;
|
||||
#[deny(unsafe_op_in_unsafe_fn)]
|
||||
#[repr(C)]
|
||||
struct LocaleMonetaryInfo {
|
||||
int_curr_symbol: &'static str,
|
||||
currency_symbol: &'static str,
|
||||
mon_decimal_point: &'static str,
|
||||
mon_thousands_sep: &'static str,
|
||||
mon_grouping: &'static [u8],
|
||||
positive_sign: &'static str,
|
||||
negative_sign: &'static str,
|
||||
int_frac_digits: u8,
|
||||
frac_digits: u8,
|
||||
p_cs_precedes: bool,
|
||||
p_sep_by_space: bool,
|
||||
p_sign_posn: u8,
|
||||
n_cs_precedes: bool,
|
||||
n_sep_by_space: bool,
|
||||
n_sign_posn: u8,
|
||||
}
|
||||
|
||||
const DEFAULT_MONETARY: LocaleMonetaryInfo = LocaleMonetaryInfo {
|
||||
int_curr_symbol: "USD ",
|
||||
currency_symbol: "$",
|
||||
mon_decimal_point: ".",
|
||||
mon_thousands_sep: ",",
|
||||
mon_grouping: &[3, 3],
|
||||
positive_sign: "",
|
||||
negative_sign: "-",
|
||||
int_frac_digits: 2,
|
||||
frac_digits: 2,
|
||||
p_cs_precedes: true,
|
||||
p_sep_by_space: false,
|
||||
p_sign_posn: 1,
|
||||
n_cs_precedes: true,
|
||||
n_sep_by_space: false,
|
||||
n_sign_posn: 1,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct FormatFlags {
|
||||
left_justify: bool,
|
||||
force_sign: bool,
|
||||
space_sign: bool,
|
||||
use_parens: bool,
|
||||
suppress_symbol: bool,
|
||||
field_width: Option<usize>,
|
||||
left_precision: Option<usize>,
|
||||
right_precision: Option<usize>,
|
||||
international: bool,
|
||||
}
|
||||
|
||||
/// Use our own floating point implementation
|
||||
/// TODO : use num-traits crate
|
||||
#[inline]
|
||||
fn my_fabs(x: f64) -> f64 {
|
||||
if x < 0.0 {
|
||||
-x
|
||||
} else {
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn my_floor(x: f64) -> f64 {
|
||||
let i = x as i64;
|
||||
if x < 0.0 && x != i as f64 {
|
||||
(i - 1) as f64
|
||||
} else {
|
||||
i as f64
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn my_trunc(x: f64) -> f64 {
|
||||
if x < 0.0 {
|
||||
-my_floor(-x)
|
||||
} else {
|
||||
my_floor(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn my_round(x: f64) -> f64 {
|
||||
my_floor(x + 0.5)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn my_pow10(n: usize) -> f64 {
|
||||
let mut result = 1.0;
|
||||
for _ in 0..n {
|
||||
result *= 10.0;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// TODO : improve grouping implementation
|
||||
fn apply_grouping(int_str: &str, monetary: &LocaleMonetaryInfo) -> String {
|
||||
let mut grouped = String::with_capacity(int_str.len() * 2);
|
||||
let mut count = 0;
|
||||
let mut group_idx = 0;
|
||||
|
||||
// The grouping array can have up to 4 elements, but the last element is always 0
|
||||
for c in int_str.chars().rev() {
|
||||
if count > 0 && count % monetary.mon_grouping[group_idx] == 0 {
|
||||
grouped.push_str(monetary.mon_thousands_sep);
|
||||
// Move to next grouping size if available
|
||||
if group_idx + 1 < monetary.mon_grouping.len() {
|
||||
group_idx += 1;
|
||||
}
|
||||
}
|
||||
grouped.push(c);
|
||||
count += 1;
|
||||
}
|
||||
// Reverse the string to get the correct order
|
||||
grouped.chars().rev().collect()
|
||||
}
|
||||
|
||||
/// Safe handling of large monetary values. Returns None if the value is too large to format
|
||||
fn format_value_parts(value: f64, frac_digits: usize) -> Option<(String, i64)> {
|
||||
let abs_value = my_fabs(value);
|
||||
if abs_value > (i64::MAX as f64) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let int_part = my_trunc(abs_value) as i64;
|
||||
let scale = my_pow10(frac_digits);
|
||||
let frac_part = my_round((abs_value - int_part as f64) * scale) as i64;
|
||||
|
||||
Some((int_part.to_string(), frac_part))
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
use super::{
|
||||
my_fabs, my_pow10, my_round, my_trunc, FormatFlags, LocaleMonetaryInfo, DEFAULT_MONETARY,
|
||||
};
|
||||
use alloc::string::{String, ToString};
|
||||
use core::{ffi::CStr, ptr, slice, str};
|
||||
|
||||
/*
|
||||
The strfmon() function formats a monetary value according to the format string format and writes the result to the character
|
||||
array s of size maxsize.
|
||||
The format string format is a string that can contain plain characters and format specifiers.
|
||||
The format specifiers start with the character '%' and are followed by one or more flags, an optional field width,
|
||||
an optional left precision, an optional right precision, and a conversion specifier.
|
||||
The conversion specifier can be either 'i' or 'n'. The 'i' specifier formats the monetary value according to the
|
||||
locale-specific rules, while the 'n' specifier formats the monetary value according to the international rules.
|
||||
|
||||
The function returns the number of characters written to the buffer, excluding the null terminator, or -1 if an error occurred.
|
||||
|
||||
*/
|
||||
pub unsafe extern "C" fn strfmon(
|
||||
s: *mut i8, // char *
|
||||
maxsize: usize, // size_t
|
||||
format: *const i8, // const char *
|
||||
mut args: ...
|
||||
) -> isize {
|
||||
if s.is_null() || format.is_null() || maxsize == 0 {
|
||||
// Check for null pointers and zero size
|
||||
return -1;
|
||||
}
|
||||
|
||||
let format_str = match unsafe { CStr::from_ptr(format) }.to_str() {
|
||||
Ok(s) => s, // Convert the format string to a string
|
||||
Err(_) => return -1, // Return -1 if the format string is not valid
|
||||
};
|
||||
// Buffer to write the formatted string
|
||||
let buffer = unsafe { slice::from_raw_parts_mut(s as *mut u8, maxsize) };
|
||||
let mut pos = 0;
|
||||
let mut format_chars = format_str.chars().peekable();
|
||||
|
||||
while let Some(c) = format_chars.next() {
|
||||
if c != '%' {
|
||||
if pos >= buffer.len() {
|
||||
return -1;
|
||||
}
|
||||
// Write the character to the buffer
|
||||
buffer[pos] = c as u8; // Write the character to the buffer
|
||||
pos += 1; // Move to the next position
|
||||
continue; // Move to the next character
|
||||
}
|
||||
// Handle '%%' as an escape sequence
|
||||
if format_chars.peek() == Some(&'%') {
|
||||
if pos >= buffer.len() {
|
||||
return -1; // Buffer overflow
|
||||
}
|
||||
buffer[pos] = b'%'; // Write '%' to the buffer
|
||||
pos += 1; // Move to the next position
|
||||
format_chars.next(); // Skip the second '%'
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut flags = FormatFlags::default();
|
||||
|
||||
// Parse flags
|
||||
while let Some(&next_char) = format_chars.peek() {
|
||||
match next_char {
|
||||
'=' => flags.left_justify = true,
|
||||
'+' => flags.force_sign = true,
|
||||
' ' => flags.space_sign = true,
|
||||
'(' => flags.use_parens = true,
|
||||
'!' => flags.suppress_symbol = true,
|
||||
_ => break,
|
||||
}
|
||||
format_chars.next();
|
||||
}
|
||||
|
||||
// Parse field width
|
||||
let mut num_str = String::new();
|
||||
while let Some(&c) = format_chars.peek() {
|
||||
if !c.is_ascii_digit() {
|
||||
break;
|
||||
}
|
||||
num_str.push(c); // Add the character to the number string
|
||||
format_chars.next(); // Move to the next character
|
||||
}
|
||||
if !num_str.is_empty() {
|
||||
flags.field_width = num_str.parse().ok(); // Parse the number string
|
||||
}
|
||||
|
||||
// Parse left precision
|
||||
if format_chars.peek() == Some(&'#') {
|
||||
format_chars.next(); // Skip the '#'
|
||||
num_str.clear(); // Clear the number string
|
||||
while let Some(&c) = format_chars.peek() {
|
||||
if !c.is_ascii_digit() {
|
||||
// Check if the character is a digit
|
||||
break;
|
||||
}
|
||||
num_str.push(c);
|
||||
format_chars.next();
|
||||
}
|
||||
flags.left_precision = num_str.parse().ok();
|
||||
}
|
||||
|
||||
// Parse right precision
|
||||
if format_chars.peek() == Some(&'.') {
|
||||
format_chars.next();
|
||||
num_str.clear();
|
||||
while let Some(&c) = format_chars.peek() {
|
||||
if !c.is_ascii_digit() {
|
||||
break;
|
||||
}
|
||||
num_str.push(c);
|
||||
format_chars.next();
|
||||
}
|
||||
flags.right_precision = num_str.parse().ok();
|
||||
}
|
||||
|
||||
match format_chars.next() {
|
||||
Some('i') => {
|
||||
flags.international = true;
|
||||
let value = unsafe { args.arg::<f64>() };
|
||||
if let Some(written) =
|
||||
format_monetary(&mut buffer[pos..], value, &DEFAULT_MONETARY, &flags)
|
||||
{
|
||||
pos += written;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Some('n') => {
|
||||
let value = unsafe { args.arg::<f64>() }; // Get the next argument as a f64
|
||||
if let Some(written) =
|
||||
format_monetary(&mut buffer[pos..], value, &DEFAULT_MONETARY, &flags)
|
||||
{
|
||||
pos += written; // Move the position by the number of characters written
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
_ => return -1,
|
||||
}
|
||||
}
|
||||
|
||||
if pos >= buffer.len() {
|
||||
return -1;
|
||||
}
|
||||
buffer[pos] = 0; // Null-terminate the buffer
|
||||
pos as isize // Return the number of characters written
|
||||
}
|
||||
|
||||
fn format_monetary(
|
||||
buffer: &mut [u8],
|
||||
value: f64,
|
||||
monetary: &LocaleMonetaryInfo,
|
||||
flags: &FormatFlags,
|
||||
) -> Option<usize> {
|
||||
let mut pos = 0; // Initialize the position to 0
|
||||
let is_negative = value < 0.0; // Check if the value is negative
|
||||
let abs_value = my_fabs(value); // Get the absolute value of the number
|
||||
// Check if the international flag is set
|
||||
let frac_digits = if flags.international {
|
||||
flags
|
||||
.right_precision
|
||||
.unwrap_or(monetary.int_frac_digits as usize)
|
||||
} else {
|
||||
flags
|
||||
.right_precision
|
||||
.unwrap_or(monetary.frac_digits as usize)
|
||||
};
|
||||
|
||||
let scale = my_pow10(frac_digits);
|
||||
let mut int_part = my_trunc(abs_value) as i64;
|
||||
let mut frac_part = my_round((abs_value - int_part as f64) * scale) as i64;
|
||||
|
||||
// Ensure that the fractional part (frac_part) doesn’t overflow due to rounding
|
||||
// when abs_value is very close to the next integer value.
|
||||
if frac_part >= scale as i64 {
|
||||
// Handle carry-over to the integer part
|
||||
int_part += 1;
|
||||
frac_part = 0;
|
||||
}
|
||||
|
||||
let mut int_str = int_part.to_string();
|
||||
|
||||
if let Some(left_prec) = flags.left_precision {
|
||||
if int_str.len() > left_prec {
|
||||
return None;
|
||||
}
|
||||
while int_str.len() < left_prec {
|
||||
int_str.insert(0, '0');
|
||||
}
|
||||
}
|
||||
|
||||
// Apply grouping
|
||||
let mut grouped = String::with_capacity(int_str.len() * 2);
|
||||
let mut group_idx = 0;
|
||||
let mut count = 0;
|
||||
|
||||
// The grouping array can have up to 4 elements, but the last element is always 0
|
||||
for c in int_str.chars().rev() {
|
||||
if count > 0
|
||||
&& count % monetary.mon_grouping[group_idx.min(monetary.mon_grouping.len() - 1)] == 0
|
||||
{
|
||||
grouped.push_str(monetary.mon_thousands_sep);
|
||||
}
|
||||
grouped.push(c);
|
||||
count += 1;
|
||||
}
|
||||
// Reverse the string to get the correct order
|
||||
let mut result = String::with_capacity(grouped.len() + 20);
|
||||
|
||||
// Add the sign
|
||||
let sign_posn = if is_negative {
|
||||
monetary.n_sign_posn
|
||||
} else {
|
||||
monetary.p_sign_posn
|
||||
};
|
||||
|
||||
// Add the sign
|
||||
let sign = if is_negative {
|
||||
monetary.negative_sign
|
||||
} else if flags.force_sign {
|
||||
monetary.positive_sign
|
||||
} else if flags.space_sign {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
// Add the sign
|
||||
if sign_posn == 0 {
|
||||
result.push('(');
|
||||
} else if sign_posn == 1 {
|
||||
result.push_str(sign);
|
||||
}
|
||||
|
||||
// Add the currency symbol
|
||||
let cs_precedes = if is_negative {
|
||||
monetary.n_cs_precedes
|
||||
} else {
|
||||
monetary.p_cs_precedes
|
||||
};
|
||||
|
||||
// Add a space between the currency symbol and the value
|
||||
let sep_by_space = if is_negative {
|
||||
monetary.n_sep_by_space
|
||||
} else {
|
||||
monetary.p_sep_by_space
|
||||
};
|
||||
|
||||
// Add the currency symbol
|
||||
let symbol = if flags.suppress_symbol {
|
||||
""
|
||||
} else if flags.international {
|
||||
monetary.int_curr_symbol
|
||||
} else {
|
||||
monetary.currency_symbol
|
||||
};
|
||||
|
||||
// Add the currency symbol
|
||||
if cs_precedes {
|
||||
result.push_str(symbol);
|
||||
if sep_by_space {
|
||||
result.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
// Add the value
|
||||
result.push_str(&grouped.chars().rev().collect::<String>());
|
||||
|
||||
if frac_digits > 0 {
|
||||
result.push_str(monetary.mon_decimal_point);
|
||||
result.push_str(&format!("{:0width$}", frac_part, width = frac_digits));
|
||||
}
|
||||
|
||||
if !cs_precedes {
|
||||
if sep_by_space {
|
||||
result.push(' ');
|
||||
}
|
||||
result.push_str(symbol);
|
||||
}
|
||||
|
||||
if sign_posn == 0 {
|
||||
result.push(')');
|
||||
}
|
||||
|
||||
if let Some(width) = flags.field_width {
|
||||
if result.len() < width {
|
||||
let padding = " ".repeat(width - result.len());
|
||||
if flags.left_justify {
|
||||
result.push_str(&padding);
|
||||
} else {
|
||||
result = padding + &result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the formatted string to the buffer
|
||||
for (i, &b) in result.as_bytes().iter().enumerate() {
|
||||
if pos + i >= buffer.len() {
|
||||
return None;
|
||||
}
|
||||
buffer[pos + i] = b;
|
||||
}
|
||||
|
||||
Some(pos + result.len())
|
||||
}
|
||||
Reference in New Issue
Block a user