| 1 | // This file is part of ICU4X. For terms of use, please see the file |
| 2 | // called LICENSE at the top level of the ICU4X source tree |
| 3 | // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). |
| 4 | |
| 5 | use crate::*; |
| 6 | use core::fmt; |
| 7 | |
| 8 | macro_rules! impl_write_num { |
| 9 | ($u:ty, $i:ty, $test:ident) => { |
| 10 | impl $crate::Writeable for $u { |
| 11 | fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result { |
| 12 | const MAX_LEN: usize = <$u>::MAX.ilog10() as usize + 1; |
| 13 | let mut buf = [b'0' ; MAX_LEN]; |
| 14 | let mut n = *self; |
| 15 | let mut i = MAX_LEN; |
| 16 | #[allow(clippy::indexing_slicing)] // n < 10^i |
| 17 | while n != 0 { |
| 18 | i -= 1; |
| 19 | buf[i] = b'0' + (n % 10) as u8; |
| 20 | n /= 10; |
| 21 | } |
| 22 | if i == MAX_LEN { |
| 23 | debug_assert_eq!(*self, 0); |
| 24 | i -= 1; |
| 25 | } |
| 26 | #[allow(clippy::indexing_slicing)] // buf is ASCII |
| 27 | let s = unsafe { core::str::from_utf8_unchecked(&buf[i..]) }; |
| 28 | sink.write_str(s) |
| 29 | } |
| 30 | |
| 31 | fn writeable_length_hint(&self) -> $crate::LengthHint { |
| 32 | LengthHint::exact(self.checked_ilog10().unwrap_or(0) as usize + 1) |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | impl $crate::Writeable for $i { |
| 37 | fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result { |
| 38 | if self.is_negative() { |
| 39 | sink.write_str("-" )?; |
| 40 | } |
| 41 | self.unsigned_abs().write_to(sink) |
| 42 | } |
| 43 | |
| 44 | fn writeable_length_hint(&self) -> $crate::LengthHint { |
| 45 | $crate::LengthHint::exact(if self.is_negative() { 1 } else { 0 }) |
| 46 | + self.unsigned_abs().writeable_length_hint() |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | #[test] |
| 51 | fn $test() { |
| 52 | use $crate::assert_writeable_eq; |
| 53 | assert_writeable_eq!(&(0 as $u), "0" ); |
| 54 | assert_writeable_eq!(&(0 as $i), "0" ); |
| 55 | assert_writeable_eq!(&(-0 as $i), "0" ); |
| 56 | assert_writeable_eq!(&(1 as $u), "1" ); |
| 57 | assert_writeable_eq!(&(1 as $i), "1" ); |
| 58 | assert_writeable_eq!(&(-1 as $i), "-1" ); |
| 59 | assert_writeable_eq!(&(9 as $u), "9" ); |
| 60 | assert_writeable_eq!(&(9 as $i), "9" ); |
| 61 | assert_writeable_eq!(&(-9 as $i), "-9" ); |
| 62 | assert_writeable_eq!(&(10 as $u), "10" ); |
| 63 | assert_writeable_eq!(&(10 as $i), "10" ); |
| 64 | assert_writeable_eq!(&(-10 as $i), "-10" ); |
| 65 | assert_writeable_eq!(&(99 as $u), "99" ); |
| 66 | assert_writeable_eq!(&(99 as $i), "99" ); |
| 67 | assert_writeable_eq!(&(-99 as $i), "-99" ); |
| 68 | assert_writeable_eq!(&(100 as $u), "100" ); |
| 69 | assert_writeable_eq!(&(-100 as $i), "-100" ); |
| 70 | assert_writeable_eq!(&<$u>::MAX, <$u>::MAX.to_string()); |
| 71 | assert_writeable_eq!(&<$i>::MAX, <$i>::MAX.to_string()); |
| 72 | assert_writeable_eq!(&<$i>::MIN, <$i>::MIN.to_string()); |
| 73 | |
| 74 | use rand::{rngs::SmallRng, Rng, SeedableRng}; |
| 75 | let mut rng = SmallRng::seed_from_u64(4); // chosen by fair dice roll. |
| 76 | // guaranteed to be random. |
| 77 | for _ in 0..1000 { |
| 78 | let rand = rng.gen::<$u>(); |
| 79 | assert_writeable_eq!(rand, rand.to_string()); |
| 80 | } |
| 81 | } |
| 82 | }; |
| 83 | } |
| 84 | |
| 85 | impl_write_num!(u8, i8, test_u8); |
| 86 | impl_write_num!(u16, i16, test_u16); |
| 87 | impl_write_num!(u32, i32, test_u32); |
| 88 | impl_write_num!(u64, i64, test_u64); |
| 89 | impl_write_num!(u128, i128, test_u128); |
| 90 | impl_write_num!(usize, isize, test_usize); |
| 91 | |
| 92 | impl Writeable for str { |
| 93 | #[inline ] |
| 94 | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
| 95 | sink.write_str(self) |
| 96 | } |
| 97 | |
| 98 | #[inline ] |
| 99 | fn writeable_length_hint(&self) -> LengthHint { |
| 100 | LengthHint::exact(self.len()) |
| 101 | } |
| 102 | |
| 103 | /// Returns a borrowed `str`. |
| 104 | /// |
| 105 | /// # Examples |
| 106 | /// |
| 107 | /// ``` |
| 108 | /// use std::borrow::Cow; |
| 109 | /// use writeable::Writeable; |
| 110 | /// |
| 111 | /// let cow = "foo" .write_to_string(); |
| 112 | /// assert!(matches!(cow, Cow::Borrowed(_))); |
| 113 | /// ``` |
| 114 | #[inline ] |
| 115 | fn write_to_string(&self) -> Cow<str> { |
| 116 | Cow::Borrowed(self) |
| 117 | } |
| 118 | |
| 119 | #[inline ] |
| 120 | fn writeable_cmp_bytes(&self, other: &[u8]) -> core::cmp::Ordering { |
| 121 | self.as_bytes().cmp(other) |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | impl Writeable for String { |
| 126 | #[inline ] |
| 127 | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
| 128 | sink.write_str(self) |
| 129 | } |
| 130 | |
| 131 | #[inline ] |
| 132 | fn writeable_length_hint(&self) -> LengthHint { |
| 133 | LengthHint::exact(self.len()) |
| 134 | } |
| 135 | |
| 136 | #[inline ] |
| 137 | fn write_to_string(&self) -> Cow<str> { |
| 138 | Cow::Borrowed(self) |
| 139 | } |
| 140 | |
| 141 | #[inline ] |
| 142 | fn writeable_cmp_bytes(&self, other: &[u8]) -> core::cmp::Ordering { |
| 143 | self.as_bytes().cmp(other) |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | impl Writeable for char { |
| 148 | #[inline ] |
| 149 | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
| 150 | sink.write_char(*self) |
| 151 | } |
| 152 | |
| 153 | #[inline ] |
| 154 | fn writeable_length_hint(&self) -> LengthHint { |
| 155 | LengthHint::exact(self.len_utf8()) |
| 156 | } |
| 157 | |
| 158 | #[inline ] |
| 159 | fn write_to_string(&self) -> Cow<str> { |
| 160 | let mut s: String = String::with_capacity(self.len_utf8()); |
| 161 | s.push(*self); |
| 162 | Cow::Owned(s) |
| 163 | } |
| 164 | |
| 165 | #[inline ] |
| 166 | fn writeable_cmp_bytes(&self, other: &[u8]) -> core::cmp::Ordering { |
| 167 | self.encode_utf8(&mut [0u8; 4]).as_bytes().cmp(other) |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | impl<T: Writeable + ?Sized> Writeable for &T { |
| 172 | #[inline ] |
| 173 | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
| 174 | (*self).write_to(sink) |
| 175 | } |
| 176 | |
| 177 | #[inline ] |
| 178 | fn write_to_parts<W: PartsWrite + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
| 179 | (*self).write_to_parts(sink) |
| 180 | } |
| 181 | |
| 182 | #[inline ] |
| 183 | fn writeable_length_hint(&self) -> LengthHint { |
| 184 | (*self).writeable_length_hint() |
| 185 | } |
| 186 | |
| 187 | #[inline ] |
| 188 | fn write_to_string(&self) -> Cow<str> { |
| 189 | (*self).write_to_string() |
| 190 | } |
| 191 | |
| 192 | #[inline ] |
| 193 | fn writeable_cmp_bytes(&self, other: &[u8]) -> core::cmp::Ordering { |
| 194 | (*self).writeable_cmp_bytes(other) |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | macro_rules! impl_write_smart_pointer { |
| 199 | ($ty:path, T: $extra_bound:path) => { |
| 200 | impl<'a, T: ?Sized + Writeable + $extra_bound> Writeable for $ty { |
| 201 | #[inline] |
| 202 | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
| 203 | core::borrow::Borrow::<T>::borrow(self).write_to(sink) |
| 204 | } |
| 205 | #[inline] |
| 206 | fn write_to_parts<W: PartsWrite + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
| 207 | core::borrow::Borrow::<T>::borrow(self).write_to_parts(sink) |
| 208 | } |
| 209 | #[inline] |
| 210 | fn writeable_length_hint(&self) -> LengthHint { |
| 211 | core::borrow::Borrow::<T>::borrow(self).writeable_length_hint() |
| 212 | } |
| 213 | #[inline] |
| 214 | fn write_to_string(&self) -> Cow<str> { |
| 215 | core::borrow::Borrow::<T>::borrow(self).write_to_string() |
| 216 | } |
| 217 | #[inline] |
| 218 | fn writeable_cmp_bytes(&self, other: &[u8]) -> core::cmp::Ordering { |
| 219 | core::borrow::Borrow::<T>::borrow(self).writeable_cmp_bytes(other) |
| 220 | } |
| 221 | } |
| 222 | }; |
| 223 | ($ty:path) => { |
| 224 | // Add a harmless duplicate Writeable bound |
| 225 | impl_write_smart_pointer!($ty, T: Writeable); |
| 226 | }; |
| 227 | } |
| 228 | |
| 229 | impl_write_smart_pointer!(Cow<'a, T>, T: alloc::borrow::ToOwned); |
| 230 | impl_write_smart_pointer!(alloc::boxed::Box<T>); |
| 231 | impl_write_smart_pointer!(alloc::rc::Rc<T>); |
| 232 | impl_write_smart_pointer!(alloc::sync::Arc<T>); |
| 233 | |
| 234 | #[test ] |
| 235 | fn test_string_impls() { |
| 236 | fn check_writeable_slice<W: Writeable + core::fmt::Display>(writeables: &[W]) { |
| 237 | assert_writeable_eq!(&writeables[0], "" ); |
| 238 | assert_writeable_eq!(&writeables[1], "abc" ); |
| 239 | assert!(matches!(writeables[0].write_to_string(), Cow::Borrowed(_))); |
| 240 | assert!(matches!(writeables[1].write_to_string(), Cow::Borrowed(_))); |
| 241 | } |
| 242 | |
| 243 | // test str impl |
| 244 | let arr: &[&str] = &["" , "abc" ]; |
| 245 | check_writeable_slice(arr); |
| 246 | |
| 247 | // test String impl |
| 248 | let arr: &[String] = &[String::new(), "abc" .to_owned()]; |
| 249 | check_writeable_slice(arr); |
| 250 | |
| 251 | // test char impl |
| 252 | let chars = ['a' , 'β' , '你' , '😀' ]; |
| 253 | for i in 0..chars.len() { |
| 254 | let s = String::from(chars[i]); |
| 255 | assert_writeable_eq!(&chars[i], s); |
| 256 | for j in 0..chars.len() { |
| 257 | assert_eq!( |
| 258 | chars[j].writeable_cmp_bytes(s.as_bytes()), |
| 259 | chars[j].cmp(&chars[i]), |
| 260 | "{:?} vs {:?}" , |
| 261 | chars[j], |
| 262 | chars[i] |
| 263 | ); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | // test Cow impl |
| 268 | let arr: &[Cow<str>] = &[Cow::Borrowed("" ), Cow::Owned("abc" .to_string())]; |
| 269 | check_writeable_slice(arr); |
| 270 | |
| 271 | // test Box impl |
| 272 | let arr: &[Box<str>] = &["" .into(), "abc" .into()]; |
| 273 | check_writeable_slice(arr); |
| 274 | |
| 275 | // test Rc impl |
| 276 | let arr: &[alloc::rc::Rc<str>] = &["" .into(), "abc" .into()]; |
| 277 | check_writeable_slice(arr); |
| 278 | |
| 279 | // test Arc impl |
| 280 | let arr: &[alloc::sync::Arc<str>] = &["" .into(), "abc" .into()]; |
| 281 | check_writeable_slice(arr); |
| 282 | |
| 283 | // test &T impl |
| 284 | let arr: &[&String] = &[&String::new(), &"abc" .to_owned()]; |
| 285 | check_writeable_slice(arr); |
| 286 | } |
| 287 | |