From 19193cd2a42bf2e355849e8cf31cc47d171d2196 Mon Sep 17 00:00:00 2001 From: "minh.pham2000" Date: Tue, 7 Mar 2023 12:35:35 +0700 Subject: [PATCH] Add locale implementation for windows --- Lib/test/test_locale.py | 4 -- stdlib/Cargo.toml | 2 +- stdlib/src/lib.rs | 2 +- stdlib/src/locale.rs | 149 ++++++++++++++++++++++++++++++---------- 4 files changed, 114 insertions(+), 43 deletions(-) diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 7afdfa331..bc8a7a35f 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -343,7 +343,6 @@ class TestFrFRNumberFormatting(FrFRCookedTest, BaseFormattingTest): class TestCollation(unittest.TestCase): # Test string collation functions - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_strcoll(self): self.assertLess(locale.strcoll('a', 'b'), 0) self.assertEqual(locale.strcoll('a', 'a'), 0) @@ -352,7 +351,6 @@ class TestCollation(unittest.TestCase): self.assertRaises(ValueError, locale.strcoll, 'a\0', 'a') self.assertRaises(ValueError, locale.strcoll, 'a', 'a\0') - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_strxfrm(self): self.assertLess(locale.strxfrm('a'), locale.strxfrm('b')) # embedded null character @@ -504,7 +502,6 @@ class NormalizeTest(unittest.TestCase): class TestMiscellaneous(unittest.TestCase): - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_defaults_UTF8(self): # Issue #18378: on (at least) macOS setting LC_CTYPE to "UTF-8" is # valid. Furthermore LC_CTYPE=UTF is used by the UTF-8 locale coercing @@ -562,7 +559,6 @@ class TestMiscellaneous(unittest.TestCase): self.assertRaises(TypeError, locale.strcoll, "a", None) self.assertRaises(TypeError, locale.strcoll, b"a", None) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_setlocale_category(self): locale.setlocale(locale.LC_ALL) locale.setlocale(locale.LC_TIME) diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index 6b76f65c8..a78ab448f 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -115,4 +115,4 @@ features = [ ] [target.'cfg(target_os = "macos")'.dependencies] -system-configuration = "0.5.0" +system-configuration = "0.5.0" \ No newline at end of file diff --git a/stdlib/src/lib.rs b/stdlib/src/lib.rs index f3a35ace6..d5f66f32b 100644 --- a/stdlib/src/lib.rs +++ b/stdlib/src/lib.rs @@ -160,7 +160,7 @@ pub fn get_module_inits() -> impl Iterator, StdlibInit { "_uuid" => uuid::make_module, } - #[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))] + #[cfg(any(unix, windows, not(any(target_os = "ios", target_os = "android", target_arch="wasm32"))))] { "_locale" => locale::make_module, } diff --git a/stdlib/src/locale.rs b/stdlib/src/locale.rs index de7bb4fc9..412dec1b6 100644 --- a/stdlib/src/locale.rs +++ b/stdlib/src/locale.rs @@ -1,7 +1,49 @@ -#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))] +#[cfg(any( + unix, + windows, + not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")) +))] pub(crate) use _locale::make_module; -#[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))] +#[cfg(not(target_arch = "wasm32"))] +#[repr(C)] +struct lconv { + decimal_point: *mut libc::c_char, + thousands_sep: *mut libc::c_char, + grouping: *mut libc::c_char, + int_curr_symbol: *mut libc::c_char, + currency_symbol: *mut libc::c_char, + mon_decimal_point: *mut libc::c_char, + mon_thousands_sep: *mut libc::c_char, + mon_grouping: *mut libc::c_char, + positive_sign: *mut libc::c_char, + negative_sign: *mut libc::c_char, + int_frac_digits: libc::c_char, + frac_digits: libc::c_char, + p_cs_precedes: libc::c_char, + p_sep_by_space: libc::c_char, + n_cs_precedes: libc::c_char, + n_sep_by_space: libc::c_char, + p_sign_posn: libc::c_char, + n_sign_posn: libc::c_char, + int_p_cs_precedes: libc::c_char, + int_n_cs_precedes: libc::c_char, + int_p_sep_by_space: libc::c_char, + int_n_sep_by_space: libc::c_char, + int_p_sign_posn: libc::c_char, + int_n_sign_posn: libc::c_char, +} + +#[cfg(not(target_arch = "wasm32"))] +extern "C" { + fn localeconv() -> *mut lconv; +} + +#[cfg(any( + unix, + windows, + not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")) +))] #[pymodule] mod _locale { use rustpython_vm::{ @@ -15,17 +57,25 @@ mod _locale { ptr, }; + #[cfg(all(unix, not(any(target_os = "ios", target_os = "android"))))] #[pyattr] use libc::{ ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, ABMON_1, ABMON_10, ABMON_11, ABMON_12, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ALT_DIGITS, AM_STR, CODESET, CRNCYSTR, DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7, - D_FMT, D_T_FMT, ERA, ERA_D_FMT, ERA_D_T_FMT, ERA_T_FMT, LC_ALL, LC_COLLATE, LC_CTYPE, - LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME, MON_1, MON_10, MON_11, MON_12, MON_2, MON_3, - MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, NOEXPR, PM_STR, RADIXCHAR, THOUSEP, T_FMT, - T_FMT_AMPM, YESEXPR, + D_FMT, D_T_FMT, ERA, ERA_D_FMT, ERA_D_T_FMT, ERA_T_FMT, LC_MESSAGES, MON_1, MON_10, MON_11, + MON_12, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, NOEXPR, PM_STR, RADIXCHAR, + THOUSEP, T_FMT, T_FMT_AMPM, YESEXPR, }; + #[pyattr] + use libc::{LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME}; + + #[cfg(any( + unix, + windows, + not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")) + ))] #[pyattr(name = "CHAR_MAX")] fn char_max(vm: &VirtualMachine) -> PyIntRef { vm.ctx.new_int(libc::c_char::MAX) @@ -58,6 +108,11 @@ mod _locale { Ok(vm.new_pyobj(string)) } + #[cfg(any( + unix, + windows, + not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")) + ))] #[pyattr(name = "Error", once)] fn error(vm: &VirtualMachine) -> PyTypeRef { vm.ctx.new_exception_type( @@ -67,6 +122,11 @@ mod _locale { ) } + #[cfg(any( + unix, + windows, + not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")) + ))] #[pyfunction] fn strcoll(string1: PyStrRef, string2: PyStrRef, vm: &VirtualMachine) -> PyResult { let cstr1 = CString::new(string1.as_str()).map_err(|e| e.to_pyexception(vm))?; @@ -74,69 +134,76 @@ mod _locale { Ok(vm.new_pyobj(unsafe { libc::strcoll(cstr1.as_ptr(), cstr2.as_ptr()) })) } + #[cfg(any( + unix, + windows, + not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")) + ))] #[pyfunction] fn strxfrm(string: PyStrRef, vm: &VirtualMachine) -> PyResult { // https://github.com/python/cpython/blob/eaae563b6878aa050b4ad406b67728b6b066220e/Modules/_localemodule.c#L390-L442 let n1 = string.byte_len() + 1; - let mut buff: Vec = vec![0; n1]; + let mut buff = vec![0u8; n1]; let cstr = CString::new(string.as_str()).map_err(|e| e.to_pyexception(vm))?; let n2 = unsafe { libc::strxfrm(buff.as_mut_ptr() as _, cstr.as_ptr(), n1) }; - buff.truncate(n2); + buff = vec![0u8; n2 + 1]; + unsafe { + libc::strxfrm(buff.as_mut_ptr() as _, cstr.as_ptr(), n2 + 1); + } Ok(vm.new_pyobj(String::from_utf8(buff).expect("strxfrm returned invalid utf-8 string"))) } - #[pyfunction] - fn localeconv(vm: &VirtualMachine) -> PyResult { + #[pyfunction(name = "localeconv")] + fn _localeconv(vm: &VirtualMachine) -> PyResult { let result = vm.ctx.new_dict(); unsafe { - let lc = libc::localeconv(); - macro_rules! set_string_field { - ($field:ident) => {{ + ($lc:expr, $field:ident) => {{ result.set_item( stringify!($field), - pystr_from_raw_cstr(vm, (*lc).$field)?, + pystr_from_raw_cstr(vm, (*$lc).$field)?, vm, )? }}; } macro_rules! set_int_field { - ($field:ident) => {{ - result.set_item(stringify!($field), vm.new_pyobj((*lc).$field), vm)? + ($lc:expr, $field:ident) => {{ + result.set_item(stringify!($field), vm.new_pyobj((*$lc).$field), vm)? }}; } macro_rules! set_group_field { - ($field:ident) => {{ + ($lc:expr, $field:ident) => {{ result.set_item( stringify!($field), - copy_grouping((*lc).$field, vm).into(), + copy_grouping((*$lc).$field, vm).into(), vm, )? }}; } - set_group_field!(mon_grouping); - set_group_field!(grouping); - set_int_field!(int_frac_digits); - set_int_field!(frac_digits); - set_int_field!(p_cs_precedes); - set_int_field!(p_sep_by_space); - set_int_field!(n_cs_precedes); - set_int_field!(p_sign_posn); - set_int_field!(n_sign_posn); - set_string_field!(decimal_point); - set_string_field!(thousands_sep); - set_string_field!(int_curr_symbol); - set_string_field!(currency_symbol); - set_string_field!(mon_decimal_point); - set_string_field!(mon_thousands_sep); - set_int_field!(n_sep_by_space); - set_string_field!(positive_sign); - set_string_field!(negative_sign); + let lc = super::localeconv(); + set_group_field!(lc, mon_grouping); + set_group_field!(lc, grouping); + set_int_field!(lc, int_frac_digits); + set_int_field!(lc, frac_digits); + set_int_field!(lc, p_cs_precedes); + set_int_field!(lc, p_sep_by_space); + set_int_field!(lc, n_cs_precedes); + set_int_field!(lc, p_sign_posn); + set_int_field!(lc, n_sign_posn); + set_string_field!(lc, decimal_point); + set_string_field!(lc, thousands_sep); + set_string_field!(lc, int_curr_symbol); + set_string_field!(lc, currency_symbol); + set_string_field!(lc, mon_decimal_point); + set_string_field!(lc, mon_thousands_sep); + set_int_field!(lc, n_sep_by_space); + set_string_field!(lc, positive_sign); + set_string_field!(lc, negative_sign); } Ok(result) } @@ -149,8 +216,17 @@ mod _locale { locale: OptionalArg>, } + #[cfg(any( + unix, + windows, + not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")) + ))] #[pyfunction] fn setlocale(args: LocaleArgs, vm: &VirtualMachine) -> PyResult { + let error = error(vm); + if cfg!(windows) && (args.category < LC_ALL || args.category > LC_TIME) { + return Err(vm.new_exception_msg(error, String::from("unsupported locale setting"))); + } unsafe { let result = match args.locale.flatten() { None => libc::setlocale(args.category, ptr::null()), @@ -161,7 +237,6 @@ mod _locale { } }; if result.is_null() { - let error = error(vm); return Err(vm.new_exception_msg(error, String::from("unsupported locale setting"))); } pystr_from_raw_cstr(vm, result)