Merge branch 'locale-h' into 'master'
Implement more locale.h functions See merge request redox-os/relibc!840
This commit is contained in:
@@ -7,6 +7,3 @@ cpp_compat = true
|
||||
|
||||
[enum]
|
||||
prefix_with_name = true
|
||||
|
||||
[export]
|
||||
include = ["locale_t"]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::platform::types::c_int;
|
||||
|
||||
pub const LC_COLLATE: c_int = 0;
|
||||
pub const LC_CTYPE: c_int = 1;
|
||||
pub const LC_MESSAGES: c_int = 2;
|
||||
pub const LC_MONETARY: c_int = 3;
|
||||
pub const LC_NUMERIC: c_int = 4;
|
||||
pub const LC_TIME: c_int = 5;
|
||||
pub const LC_ALL: c_int = 6;
|
||||
pub const LC_COLLATE_MASK: c_int = 0x1;
|
||||
pub const LC_CTYPE_MASK: c_int = 0x2;
|
||||
pub const LC_MESSAGES_MASK: c_int = 0x4;
|
||||
pub const LC_MONETARY_MASK: c_int = 0x8;
|
||||
pub const LC_NUMERIC_MASK: c_int = 0x10;
|
||||
pub const LC_TIME_MASK: c_int = 0x20;
|
||||
pub const LC_ALL_MASK: c_int = 0b111111;
|
||||
@@ -0,0 +1,395 @@
|
||||
use core::str::FromStr;
|
||||
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
ffi::CString,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use super::constants::*;
|
||||
use crate::platform::types::{c_char, c_int};
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html>.
|
||||
/// this struct is not ordered like in the posix spec for readability
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct lconv {
|
||||
pub decimal_point: *mut c_char,
|
||||
pub thousands_sep: *mut c_char,
|
||||
pub grouping: *mut c_char,
|
||||
pub int_curr_symbol: *mut c_char,
|
||||
pub currency_symbol: *mut c_char,
|
||||
pub mon_decimal_point: *mut c_char,
|
||||
pub mon_thousands_sep: *mut c_char,
|
||||
pub mon_grouping: *mut c_char,
|
||||
pub positive_sign: *mut c_char,
|
||||
pub negative_sign: *mut c_char,
|
||||
pub int_frac_digits: c_char,
|
||||
pub frac_digits: c_char,
|
||||
pub p_cs_precedes: c_char,
|
||||
pub p_sep_by_space: c_char,
|
||||
pub n_cs_precedes: c_char,
|
||||
pub n_sep_by_space: c_char,
|
||||
pub p_sign_posn: c_char,
|
||||
pub n_sign_posn: c_char,
|
||||
pub int_p_cs_precedes: c_char,
|
||||
pub int_p_sep_by_space: c_char,
|
||||
pub int_n_cs_precedes: c_char,
|
||||
pub int_n_sep_by_space: c_char,
|
||||
pub int_p_sign_posn: c_char,
|
||||
pub int_n_sign_posn: c_char,
|
||||
}
|
||||
unsafe impl Sync for lconv {}
|
||||
|
||||
/// "POSIX" or "C" default
|
||||
pub(crate) const fn posix_lconv() -> lconv {
|
||||
lconv {
|
||||
// numeric, non-monetary
|
||||
decimal_point: b".\0".as_ptr() as *mut c_char,
|
||||
thousands_sep: b"\0".as_ptr() as *mut c_char,
|
||||
grouping: b"\0".as_ptr() as *mut c_char,
|
||||
// local monetary
|
||||
int_curr_symbol: b"\0".as_ptr() as *mut c_char,
|
||||
currency_symbol: b"\0".as_ptr() as *mut c_char,
|
||||
mon_decimal_point: b"\0".as_ptr() as *mut c_char,
|
||||
mon_thousands_sep: b"\0".as_ptr() as *mut c_char,
|
||||
mon_grouping: b"\0".as_ptr() as *mut c_char,
|
||||
positive_sign: b"\0".as_ptr() as *mut c_char,
|
||||
negative_sign: b"\0".as_ptr() as *mut c_char,
|
||||
// delimiters, unspecified
|
||||
int_frac_digits: c_char::MAX,
|
||||
frac_digits: c_char::MAX,
|
||||
p_cs_precedes: c_char::MAX,
|
||||
p_sep_by_space: c_char::MAX,
|
||||
n_cs_precedes: c_char::MAX,
|
||||
n_sep_by_space: c_char::MAX,
|
||||
p_sign_posn: c_char::MAX,
|
||||
n_sign_posn: c_char::MAX,
|
||||
// international format
|
||||
int_p_cs_precedes: c_char::MAX,
|
||||
int_p_sep_by_space: c_char::MAX,
|
||||
int_n_cs_precedes: c_char::MAX,
|
||||
int_n_sep_by_space: c_char::MAX,
|
||||
int_p_sign_posn: c_char::MAX,
|
||||
int_n_sign_posn: c_char::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct LocaleData {
|
||||
pub name: CString,
|
||||
pub lconv: lconv,
|
||||
// Owned memory buffers
|
||||
pub decimal_point: CString,
|
||||
pub thousands_sep: CString,
|
||||
pub grouping: Vec<c_char>,
|
||||
pub int_curr_symbol: CString,
|
||||
pub currency_symbol: CString,
|
||||
pub mon_decimal_point: CString,
|
||||
pub mon_thousands_sep: CString,
|
||||
pub mon_grouping: Vec<c_char>,
|
||||
pub positive_sign: CString,
|
||||
pub negative_sign: CString,
|
||||
}
|
||||
unsafe impl Sync for LocaleData {}
|
||||
|
||||
impl LocaleData {
|
||||
pub fn new(name: CString, defs: PosixLocaleDef) -> Box<Self> {
|
||||
let mut data = Box::new(LocaleData {
|
||||
name,
|
||||
decimal_point: Self::to_cstring(defs.decimal_point),
|
||||
thousands_sep: Self::to_cstring(defs.thousands_sep),
|
||||
grouping: Self::to_grouping_char(defs.grouping),
|
||||
int_curr_symbol: Self::to_cstring(defs.int_curr_symbol),
|
||||
currency_symbol: Self::to_cstring(defs.currency_symbol),
|
||||
mon_decimal_point: Self::to_cstring(defs.mon_decimal_point),
|
||||
mon_thousands_sep: Self::to_cstring(defs.mon_thousands_sep),
|
||||
mon_grouping: Self::to_grouping_char(defs.mon_grouping),
|
||||
positive_sign: Self::to_cstring(defs.positive_sign),
|
||||
negative_sign: Self::to_cstring(defs.negative_sign),
|
||||
lconv: unsafe { core::mem::zeroed() },
|
||||
});
|
||||
|
||||
data.lconv.int_frac_digits = defs.int_frac_digits.unwrap_or(c_char::MAX);
|
||||
data.lconv.frac_digits = defs.frac_digits.unwrap_or(c_char::MAX);
|
||||
data.lconv.p_cs_precedes = defs.p_cs_precedes.unwrap_or(c_char::MAX);
|
||||
data.lconv.p_sep_by_space = defs.p_sep_by_space.unwrap_or(c_char::MAX);
|
||||
data.lconv.n_cs_precedes = defs.n_cs_precedes.unwrap_or(c_char::MAX);
|
||||
data.lconv.n_sep_by_space = defs.n_sep_by_space.unwrap_or(c_char::MAX);
|
||||
data.lconv.p_sign_posn = defs.p_sign_posn.unwrap_or(c_char::MAX);
|
||||
data.lconv.n_sign_posn = defs.n_sign_posn.unwrap_or(c_char::MAX);
|
||||
data.lconv.int_p_cs_precedes = defs.int_p_cs_precedes.unwrap_or(c_char::MAX);
|
||||
data.lconv.int_p_sep_by_space = defs.int_p_sep_by_space.unwrap_or(c_char::MAX);
|
||||
data.lconv.int_n_cs_precedes = defs.int_n_cs_precedes.unwrap_or(c_char::MAX);
|
||||
data.lconv.int_n_sep_by_space = defs.int_n_sep_by_space.unwrap_or(c_char::MAX);
|
||||
data.lconv.int_p_sign_posn = defs.int_p_sign_posn.unwrap_or(c_char::MAX);
|
||||
data.lconv.int_n_sign_posn = defs.int_n_sign_posn.unwrap_or(c_char::MAX);
|
||||
|
||||
data.update_lconv_pointers();
|
||||
data
|
||||
}
|
||||
|
||||
pub fn posix() -> Box<Self> {
|
||||
LocaleData::new(CString::from_str("C").unwrap(), PosixLocaleDef::default())
|
||||
}
|
||||
|
||||
fn update_lconv_pointers(&mut self) {
|
||||
self.lconv.decimal_point = self.decimal_point.as_ptr() as *mut c_char;
|
||||
self.lconv.thousands_sep = self.thousands_sep.as_ptr() as *mut c_char;
|
||||
self.lconv.grouping = self.grouping.as_ptr() as *mut c_char;
|
||||
self.lconv.int_curr_symbol = self.int_curr_symbol.as_ptr() as *mut c_char;
|
||||
self.lconv.currency_symbol = self.currency_symbol.as_ptr() as *mut c_char;
|
||||
self.lconv.mon_decimal_point = self.mon_decimal_point.as_ptr() as *mut c_char;
|
||||
self.lconv.mon_thousands_sep = self.mon_thousands_sep.as_ptr() as *mut c_char;
|
||||
self.lconv.mon_grouping = self.mon_grouping.as_ptr() as *mut c_char;
|
||||
self.lconv.positive_sign = self.positive_sign.as_ptr() as *mut c_char;
|
||||
self.lconv.negative_sign = self.negative_sign.as_ptr() as *mut c_char;
|
||||
}
|
||||
|
||||
pub fn copy_category(&mut self, other: &Self, category: c_int) {
|
||||
match category {
|
||||
LC_NUMERIC => {
|
||||
self.decimal_point = other.decimal_point.clone();
|
||||
self.thousands_sep = other.thousands_sep.clone();
|
||||
self.grouping = other.grouping.clone();
|
||||
self.lconv.frac_digits = other.lconv.frac_digits;
|
||||
}
|
||||
LC_MONETARY => {
|
||||
self.int_curr_symbol = other.int_curr_symbol.clone();
|
||||
self.currency_symbol = other.currency_symbol.clone();
|
||||
self.mon_decimal_point = other.mon_decimal_point.clone();
|
||||
self.mon_thousands_sep = other.mon_thousands_sep.clone();
|
||||
self.mon_grouping = other.mon_grouping.clone();
|
||||
self.positive_sign = other.positive_sign.clone();
|
||||
self.negative_sign = other.negative_sign.clone();
|
||||
self.lconv.int_frac_digits = other.lconv.int_frac_digits;
|
||||
self.lconv.p_cs_precedes = other.lconv.p_cs_precedes;
|
||||
self.lconv.p_sep_by_space = other.lconv.p_sep_by_space;
|
||||
self.lconv.n_cs_precedes = other.lconv.n_cs_precedes;
|
||||
self.lconv.n_sep_by_space = other.lconv.n_sep_by_space;
|
||||
self.lconv.p_sign_posn = other.lconv.p_sign_posn;
|
||||
self.lconv.n_sign_posn = other.lconv.n_sign_posn;
|
||||
self.lconv.int_p_cs_precedes = other.lconv.int_p_cs_precedes;
|
||||
self.lconv.int_p_sep_by_space = other.lconv.int_p_sep_by_space;
|
||||
self.lconv.int_n_cs_precedes = other.lconv.int_n_cs_precedes;
|
||||
self.lconv.int_n_sep_by_space = other.lconv.int_n_sep_by_space;
|
||||
self.lconv.int_p_sign_posn = other.lconv.int_p_sign_posn;
|
||||
self.lconv.int_n_sign_posn = other.lconv.int_n_sign_posn;
|
||||
}
|
||||
LC_ALL => {
|
||||
*self = other.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.update_lconv_pointers();
|
||||
}
|
||||
|
||||
fn to_cstring(opt: Option<CString>) -> CString {
|
||||
opt.unwrap_or_else(|| CString::new("").unwrap())
|
||||
}
|
||||
|
||||
fn to_grouping_char(opt: Vec<Option<c_char>>) -> Vec<c_char> {
|
||||
let mut v: Vec<c_char> = opt.into_iter().map(Self::to_char).collect();
|
||||
v.push(0);
|
||||
v
|
||||
}
|
||||
|
||||
fn to_char(opt: Option<c_char>) -> c_char {
|
||||
opt.unwrap_or(c_char::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for LocaleData {
|
||||
fn clone(&self) -> Self {
|
||||
let mut data = Self {
|
||||
name: self.name.clone(),
|
||||
lconv: self.lconv.clone(),
|
||||
decimal_point: self.decimal_point.clone(),
|
||||
thousands_sep: self.thousands_sep.clone(),
|
||||
grouping: self.grouping.clone(),
|
||||
int_curr_symbol: self.int_curr_symbol.clone(),
|
||||
currency_symbol: self.currency_symbol.clone(),
|
||||
mon_decimal_point: self.mon_decimal_point.clone(),
|
||||
mon_thousands_sep: self.mon_thousands_sep.clone(),
|
||||
mon_grouping: self.mon_grouping.clone(),
|
||||
positive_sign: self.positive_sign.clone(),
|
||||
negative_sign: self.negative_sign.clone(),
|
||||
};
|
||||
data.update_lconv_pointers();
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct GlobalLocaleData {
|
||||
// names per LC_* constant
|
||||
pub names: [CString; 7],
|
||||
pub data: LocaleData,
|
||||
}
|
||||
|
||||
impl GlobalLocaleData {
|
||||
pub fn new() -> Box<Self> {
|
||||
let data = LocaleData::posix();
|
||||
let names = [
|
||||
data.name.clone(),
|
||||
data.name.clone(),
|
||||
data.name.clone(),
|
||||
data.name.clone(),
|
||||
data.name.clone(),
|
||||
data.name.clone(),
|
||||
data.name.clone(),
|
||||
];
|
||||
let mut r = Box::new(GlobalLocaleData { data: *data, names });
|
||||
r.data.update_lconv_pointers();
|
||||
r
|
||||
}
|
||||
|
||||
pub fn get_name(&self, category: i32) -> Option<&CString> {
|
||||
self.names.get(category as usize)
|
||||
}
|
||||
pub fn set_name(&mut self, category: i32, name: CString) -> Option<&CString> {
|
||||
if self.names.get(category as usize).is_some() {
|
||||
self.names[category as usize] = name;
|
||||
self.names.get(category as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe impl Sync for GlobalLocaleData {}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PosixLocaleDef {
|
||||
pub decimal_point: Option<CString>,
|
||||
pub thousands_sep: Option<CString>,
|
||||
pub grouping: Vec<Option<c_char>>,
|
||||
pub int_curr_symbol: Option<CString>,
|
||||
pub currency_symbol: Option<CString>,
|
||||
pub mon_decimal_point: Option<CString>,
|
||||
pub mon_thousands_sep: Option<CString>,
|
||||
pub mon_grouping: Vec<Option<c_char>>,
|
||||
pub positive_sign: Option<CString>,
|
||||
pub negative_sign: Option<CString>,
|
||||
pub int_frac_digits: Option<c_char>,
|
||||
pub frac_digits: Option<c_char>,
|
||||
pub p_cs_precedes: Option<c_char>,
|
||||
pub p_sep_by_space: Option<c_char>,
|
||||
pub n_cs_precedes: Option<c_char>,
|
||||
pub n_sep_by_space: Option<c_char>,
|
||||
pub p_sign_posn: Option<c_char>,
|
||||
pub n_sign_posn: Option<c_char>,
|
||||
pub int_p_cs_precedes: Option<c_char>,
|
||||
pub int_p_sep_by_space: Option<c_char>,
|
||||
pub int_n_cs_precedes: Option<c_char>,
|
||||
pub int_n_sep_by_space: Option<c_char>,
|
||||
pub int_p_sign_posn: Option<c_char>,
|
||||
pub int_n_sign_posn: Option<c_char>,
|
||||
}
|
||||
impl PosixLocaleDef {
|
||||
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap07.html>
|
||||
pub fn parse(content: &str) -> Self {
|
||||
let mut locale = PosixLocaleDef::default();
|
||||
|
||||
let mut lines = content.lines();
|
||||
loop {
|
||||
let Some(line) = lines.next() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut parts = trimmed.split_ascii_whitespace();
|
||||
let Some(key) = parts.next() else {
|
||||
continue;
|
||||
};
|
||||
let mut val: String = String::new();
|
||||
loop {
|
||||
let Some(chunk) = parts.next() else {
|
||||
break;
|
||||
};
|
||||
if !chunk.ends_with('\\') {
|
||||
val.push_str(chunk);
|
||||
break;
|
||||
}
|
||||
// multiline values
|
||||
val.push_str(&chunk[0..chunk.len() - 1]);
|
||||
let Some(next_line) = lines.next() else {
|
||||
break;
|
||||
};
|
||||
parts = next_line.split_ascii_whitespace();
|
||||
}
|
||||
|
||||
if key.is_empty() || val.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match key {
|
||||
"decimal_point" => locale.decimal_point = Self::parse_str(&val),
|
||||
"thousands_sep" => locale.thousands_sep = Self::parse_str(&val),
|
||||
"int_curr_symbol" => locale.int_curr_symbol = Self::parse_str(&val),
|
||||
"currency_symbol" => locale.currency_symbol = Self::parse_str(&val),
|
||||
"mon_decimal_point" => locale.mon_decimal_point = Self::parse_str(&val),
|
||||
"mon_thousands_sep" => locale.mon_thousands_sep = Self::parse_str(&val),
|
||||
"positive_sign" => locale.positive_sign = Self::parse_str(&val),
|
||||
"negative_sign" => locale.negative_sign = Self::parse_str(&val),
|
||||
"grouping" => locale.grouping = Self::parse_int_group(&val),
|
||||
"mon_grouping" => locale.mon_grouping = Self::parse_int_group(&val),
|
||||
"int_frac_digits" => locale.int_frac_digits = Self::parse_int(&val),
|
||||
"frac_digits" => locale.frac_digits = Self::parse_int(&val),
|
||||
"p_cs_precedes" => locale.p_cs_precedes = Self::parse_int(&val),
|
||||
"p_sep_by_space" => locale.p_sep_by_space = Self::parse_int(&val),
|
||||
"n_cs_precedes" => locale.n_cs_precedes = Self::parse_int(&val),
|
||||
"n_sep_by_space" => locale.n_sep_by_space = Self::parse_int(&val),
|
||||
"p_sign_posn" => locale.p_sign_posn = Self::parse_int(&val),
|
||||
"n_sign_posn" => locale.n_sign_posn = Self::parse_int(&val),
|
||||
"int_p_cs_precedes" => locale.int_p_cs_precedes = Self::parse_int(&val),
|
||||
"int_p_sep_by_space" => locale.int_p_sep_by_space = Self::parse_int(&val),
|
||||
"int_n_cs_precedes" => locale.int_n_cs_precedes = Self::parse_int(&val),
|
||||
"int_n_sep_by_space" => locale.int_n_sep_by_space = Self::parse_int(&val),
|
||||
"int_p_sign_posn" => locale.int_p_sign_posn = Self::parse_int(&val),
|
||||
"int_n_sign_posn" => locale.int_n_sign_posn = Self::parse_int(&val),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
locale
|
||||
}
|
||||
|
||||
/// parse e.g. `3;3;0` -> [ 3,3,0 ], `-1` -> [ None ]
|
||||
fn parse_int_group(val: &str) -> Vec<Option<c_char>> {
|
||||
val.split(';').map(Self::parse_int).collect()
|
||||
}
|
||||
|
||||
/// parse e.g. `-1` -> None, `1` -> Some(1)
|
||||
fn parse_int(val: &str) -> Option<c_char> {
|
||||
let r = val.trim().parse::<c_char>().ok();
|
||||
if r.is_some_and(|i| i < 0) {
|
||||
return None;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// parse e.g. `""`
|
||||
fn parse_str(val: &str) -> Option<CString> {
|
||||
let mut r = String::new();
|
||||
let mut v = val.chars();
|
||||
if v.next() != Some('"') {
|
||||
return None;
|
||||
}
|
||||
while let Some(c) = v.next() {
|
||||
if c == '"' {
|
||||
if v.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// TODO: Parse <..>
|
||||
r.push(c);
|
||||
}
|
||||
CString::new(r).ok()
|
||||
}
|
||||
}
|
||||
+151
-54
@@ -2,73 +2,170 @@
|
||||
//!
|
||||
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html>.
|
||||
|
||||
use core::ptr;
|
||||
use alloc::{boxed::Box, ffi::CString, string::String};
|
||||
use core::{ptr, str::FromStr};
|
||||
|
||||
use crate::platform::types::{c_char, c_int, c_void};
|
||||
use crate::{
|
||||
c_str::CStr,
|
||||
error::{Errno, ResultExtPtrMut},
|
||||
fs::File,
|
||||
header::{errno, fcntl},
|
||||
io::Read,
|
||||
platform::types::{c_char, c_int, c_void},
|
||||
sync::Once,
|
||||
};
|
||||
|
||||
const EMPTY_PTR: *const c_char = "\0" as *const _ as *const c_char;
|
||||
// Can't use &str because of the mutability
|
||||
static mut C_LOCALE: [c_char; 2] = [b'C' as c_char, 0];
|
||||
|
||||
//TODO: locale_t internals
|
||||
mod constants;
|
||||
use constants::*;
|
||||
mod data;
|
||||
use data::*;
|
||||
|
||||
pub type locale_t = *mut c_void;
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html>.
|
||||
#[repr(C)]
|
||||
pub struct lconv {
|
||||
currency_symbol: *const c_char,
|
||||
decimal_point: *const c_char,
|
||||
frac_digits: c_char,
|
||||
grouping: *const c_char,
|
||||
int_curr_symbol: *const c_char,
|
||||
int_frac_digits: c_char,
|
||||
mon_decimal_point: *const c_char,
|
||||
mon_grouping: *const c_char,
|
||||
mon_thousands_sep: *const c_char,
|
||||
negative_sign: *const c_char,
|
||||
n_cs_precedes: c_char,
|
||||
n_sep_by_space: c_char,
|
||||
n_sign_posn: c_char,
|
||||
positive_sign: *const c_char,
|
||||
p_cs_precedes: c_char,
|
||||
p_sep_by_space: c_char,
|
||||
p_sign_posn: c_char,
|
||||
thousands_sep: *const c_char,
|
||||
}
|
||||
unsafe impl Sync for lconv {}
|
||||
|
||||
// Mutable because POSIX demands a mutable pointer, even though it warns
|
||||
// against mutating it
|
||||
static mut CURRENT_LOCALE: lconv = lconv {
|
||||
currency_symbol: EMPTY_PTR,
|
||||
decimal_point: ".\0" as *const _ as *const c_char,
|
||||
frac_digits: c_char::max_value(),
|
||||
grouping: EMPTY_PTR,
|
||||
int_curr_symbol: EMPTY_PTR,
|
||||
int_frac_digits: c_char::max_value(),
|
||||
mon_decimal_point: EMPTY_PTR,
|
||||
mon_grouping: EMPTY_PTR,
|
||||
mon_thousands_sep: EMPTY_PTR,
|
||||
negative_sign: EMPTY_PTR,
|
||||
n_cs_precedes: c_char::max_value(),
|
||||
n_sep_by_space: c_char::max_value(),
|
||||
n_sign_posn: c_char::max_value(),
|
||||
positive_sign: EMPTY_PTR,
|
||||
p_cs_precedes: c_char::max_value(),
|
||||
p_sep_by_space: c_char::max_value(),
|
||||
p_sign_posn: c_char::max_value(),
|
||||
thousands_sep: EMPTY_PTR,
|
||||
};
|
||||
/// constant struct to "C" or "POSIX" locale
|
||||
/// mutable because POSIX demands a mutable pointer
|
||||
static mut POSIX_LOCALE: lconv = posix_lconv();
|
||||
pub const LC_GLOBAL_LOCALE: locale_t = -1isize as locale_t;
|
||||
/// process-wide locale, used by setlocale() and localeconv()
|
||||
static mut GLOBAL_LOCALE: *mut GlobalLocaleData = ptr::null_mut();
|
||||
/// thread-wide locale, used by uselocale() and localeconv()
|
||||
#[thread_local]
|
||||
static mut THREAD_LOCALE: *mut LocaleData = ptr::null_mut();
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/localeconv.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn localeconv() -> *mut lconv {
|
||||
&raw mut CURRENT_LOCALE as *mut _
|
||||
let current = uselocale(ptr::null_mut());
|
||||
if current == LC_GLOBAL_LOCALE || current.is_null() {
|
||||
if !GLOBAL_LOCALE.is_null() {
|
||||
// safety: GLOBAL_LOCALE is never set to null again
|
||||
&raw mut (*GLOBAL_LOCALE).data.lconv
|
||||
} else {
|
||||
&raw mut POSIX_LOCALE
|
||||
}
|
||||
} else {
|
||||
let current = current as *mut LocaleData;
|
||||
&raw mut (*current).lconv
|
||||
}
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/setlocale.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn setlocale(_option: c_int, _val: *const c_char) -> *mut c_char {
|
||||
// TODO actually implement
|
||||
&raw mut C_LOCALE as *mut c_char
|
||||
pub unsafe extern "C" fn setlocale(category: c_int, locale: *const c_char) -> *mut c_char {
|
||||
if GLOBAL_LOCALE.is_null() {
|
||||
let new_global = GlobalLocaleData::new();
|
||||
GLOBAL_LOCALE = Box::into_raw(new_global);
|
||||
};
|
||||
let Some(mut global) = GLOBAL_LOCALE.as_mut() else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
|
||||
if locale.is_null() {
|
||||
let Some(name) = global.get_name(category) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
return name.as_ptr() as *mut c_char;
|
||||
}
|
||||
|
||||
let name = CStr::from_ptr(locale).to_str().unwrap_or("C");
|
||||
|
||||
match load_locale_file(name) {
|
||||
Ok(loc_ptr) => {
|
||||
global.data.copy_category(&loc_ptr, category);
|
||||
let Some(name) = global.set_name(category, CString::from_str(name).unwrap()) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
name.as_ptr() as *mut c_char
|
||||
}
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn uselocale(newloc: locale_t) -> locale_t {
|
||||
let old_loc = if THREAD_LOCALE.is_null() {
|
||||
LC_GLOBAL_LOCALE
|
||||
} else {
|
||||
THREAD_LOCALE as locale_t
|
||||
};
|
||||
|
||||
if !newloc.is_null() {
|
||||
THREAD_LOCALE = if newloc == LC_GLOBAL_LOCALE {
|
||||
ptr::null_mut()
|
||||
} else {
|
||||
newloc as *mut LocaleData
|
||||
};
|
||||
}
|
||||
|
||||
old_loc
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/newlocale.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn newlocale(mask: c_int, locale: *const c_char, base: locale_t) -> locale_t {
|
||||
let name = CStr::from_ptr(locale).to_string_lossy().into_owned();
|
||||
let name = name.as_str();
|
||||
let mut new_locale = if name == "" || name == "C" || name == "POSIX" {
|
||||
Ok(LocaleData::posix())
|
||||
} else {
|
||||
load_locale_file(name)
|
||||
};
|
||||
if base != LC_GLOBAL_LOCALE {
|
||||
// borrowing here
|
||||
let base = base as *const _ as *const LocaleData;
|
||||
if let Ok(new_locale) = new_locale.as_mut() {
|
||||
if let Some(base) = base.as_ref() {
|
||||
// copy old values if not containing the mask
|
||||
if (mask & LC_NUMERIC_MASK) == 0 {
|
||||
new_locale.copy_category(base, LC_NUMERIC);
|
||||
}
|
||||
if (mask & LC_MONETARY_MASK) == 0 {
|
||||
new_locale.copy_category(base, LC_MONETARY);
|
||||
}
|
||||
// TODO: other categories?
|
||||
}
|
||||
}
|
||||
}
|
||||
new_locale.or_errno_null_mut() as *mut _
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/freelocale.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn freelocale(loc: locale_t) {
|
||||
if !loc.is_null() && loc != LC_GLOBAL_LOCALE {
|
||||
drop(Box::from_raw(loc as *mut LocaleData));
|
||||
}
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/duplocale.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn duplocale(loc: locale_t) -> locale_t {
|
||||
if loc.is_null() {
|
||||
// TODO: errno?
|
||||
loc
|
||||
} else if loc == LC_GLOBAL_LOCALE {
|
||||
Box::into_raw(LocaleData::posix()) as locale_t
|
||||
} else {
|
||||
// borrowing here
|
||||
let loc = loc as *const _ as *const LocaleData;
|
||||
Box::into_raw(Box::from((*loc).clone())) as locale_t
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_locale_file(name: &str) -> Result<Box<LocaleData>, Errno> {
|
||||
let mut path = String::from("/usr/share/i18n/locales/");
|
||||
path.push_str(name);
|
||||
|
||||
let path_c = CString::new(path).map_err(|_| Errno(errno::EINVAL))?;
|
||||
let mut content = String::new();
|
||||
|
||||
let mut file = File::open(path_c.as_c_str().into(), fcntl::O_RDONLY)?;
|
||||
file.read_to_string(&mut content)
|
||||
.map_err(|_| Errno(errno::EIO))?;
|
||||
|
||||
let toml = PosixLocaleDef::parse(&content);
|
||||
Ok(LocaleData::new(CString::from_str(name).unwrap(), toml))
|
||||
}
|
||||
|
||||
+3
-1
@@ -46,7 +46,9 @@ EXPECT_NAMES=\
|
||||
glob \
|
||||
iso646 \
|
||||
libgen \
|
||||
locale \
|
||||
locale/duplocale \
|
||||
locale/newlocale \
|
||||
locale/setlocale \
|
||||
math \
|
||||
regex \
|
||||
select \
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int main() {
|
||||
struct lconv *lconv0 = localeconv();
|
||||
|
||||
locale_t locale0 = uselocale(NULL);
|
||||
assert(locale0 == LC_GLOBAL_LOCALE);
|
||||
|
||||
locale_t locale1 = uselocale(LC_GLOBAL_LOCALE);
|
||||
assert(locale1 == LC_GLOBAL_LOCALE);
|
||||
|
||||
struct lconv *lconv1 = localeconv();
|
||||
assert(lconv0 == lconv1);
|
||||
|
||||
locale_t locale2 = newlocale(LC_ALL_MASK, "C", (locale_t)0);
|
||||
assert(locale2 != LC_GLOBAL_LOCALE);
|
||||
|
||||
struct lconv *lconv2 = localeconv();
|
||||
assert(lconv2 == lconv1);
|
||||
|
||||
locale_t locale3 = duplocale(locale2);
|
||||
assert(locale3 != locale2);
|
||||
assert(locale3 != LC_GLOBAL_LOCALE);
|
||||
|
||||
locale_t locale4 = uselocale(locale3);
|
||||
assert(locale4 == locale1);
|
||||
|
||||
struct lconv *lconv3 = localeconv();
|
||||
assert(lconv3 != lconv2);
|
||||
|
||||
// should not crash
|
||||
freelocale(locale3);
|
||||
freelocale(locale2);
|
||||
freelocale(locale1);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int main() {
|
||||
locale_t locale0 = newlocale(LC_ALL_MASK, "C", (locale_t)0);
|
||||
assert(locale0 != (locale_t)0);
|
||||
|
||||
locale_t locale1 = newlocale(LC_ALL_MASK, "non-existent-locale", (locale_t)0);
|
||||
assert(locale1 == (locale_t)0);
|
||||
|
||||
// TODO: locale files inside redoxer (linux and redox)?
|
||||
// locale_t locale2 = newlocale(LC_ALL_MASK, "en_US", (locale_t)0);
|
||||
// assert(locale2 != (locale_t)0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user