Files
RedBear-OS/src/header/monetary/strfmon.rs
T
2024-12-26 19:10:57 +01:00

310 lines
9.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::{FormatFlags, LocaleMonetaryInfo, DEFAULT_MONETARY};
use alloc::string::{String, ToString};
use core::{ffi::CStr, ptr, slice, str};
use libm::{fabs, floor, pow, round, trunc};
/*
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.
TODO : better handling error : always return -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 = fabs(value); // Get the absolute value of the number
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 = pow(10.0, frac_digits as f64);
let mut int_part = trunc(abs_value) as i64;
let mut frac_part = round((abs_value - int_part as f64) * scale) as i64;
// Ensure that the fractional part (frac_part) doesnt 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 position
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 at the beginning
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 {
//let scale = my_pow10(frac_digits);
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 {
// Add the currency symbol after the value
if sep_by_space {
result.push(' ');
}
result.push_str(symbol);
}
if sign_posn == 0 {
result.push(')');
}
if let Some(width) = flags.field_width {
// Check if the field width is specified
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())
}