From ba4e2af199812c14c78be3b8278526a2ecc2c1e9 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Fri, 15 Dec 2023 05:05:10 +0100 Subject: [PATCH] core: add core::ascii::Char::from_digit Since core::ascii::Char::digit returns Some for decimal digits only, introduce core::ascii::Char::from_digit method which handles values up to 35. It mimics char::from_digit though it forgoes the radix parameter. Issue: https://github.com/rust-lang/libs-team/issues/179 Issue: https://github.com/rust-lang/rust/issues/110998 --- library/core/src/ascii/ascii_char.rs | 60 ++++++++++++++++++++-------- library/core/src/char/convert.rs | 12 +++--- library/core/tests/ascii.rs | 38 ++++++++++++++++++ 3 files changed, 89 insertions(+), 21 deletions(-) diff --git a/library/core/src/ascii/ascii_char.rs b/library/core/src/ascii/ascii_char.rs index cc872a5343d6a..bfb6305c66fc1 100644 --- a/library/core/src/ascii/ascii_char.rs +++ b/library/core/src/ascii/ascii_char.rs @@ -3,7 +3,7 @@ //! suggestions from rustc if you get anything slightly wrong in here, and overall //! helps with clarity as we're also referring to `char` intentionally in here. -use crate::fmt::{self, Write}; +use crate::fmt; use crate::mem::transmute; /// One of the 128 Unicode characters from U+0000 through U+007F, @@ -474,7 +474,8 @@ impl AsciiChar { /// When passed the *number* `0`, `1`, …, `9`, returns the *character* /// `'0'`, `'1'`, …, `'9'` respectively. /// - /// If `d >= 10`, returns `None`. + /// If `d >= 10`, returns `None`. To get a digit up to `d == 35`, use + /// [`from_digit`](Self::from_digit) instead. #[unstable(feature = "ascii_char", issue = "110998")] #[inline] pub const fn digit(d: u8) -> Option { @@ -515,6 +516,37 @@ impl AsciiChar { } } + /// Returns a *character* digit corresponding to given numeric value of + /// a digit. + /// + /// When passed the *number* `0`, `1`, …, `8`, `9`, `10`, …, `34`, `35`, + /// returns the *character* `'0'`, `'1'`, …, `'8'`, `'9'`, `'a'`, …, `'y'`, + /// `'z'` respectively. + /// + /// If `d >= 36`, returns `None`. To get a digit only for `d < 10`, use + /// [`digit`](Self::digit) instead. + /// + /// # Example + /// + /// ``` + /// #![feature(ascii_char, ascii_char_variants)] + /// use core::ascii::Char; + /// + /// assert_eq!(Some(Char::Digit0), Char::from_digit(0)); + /// assert_eq!(Some(Char::Digit9), Char::from_digit(9)); + /// assert_eq!(Some(Char::SmallA), Char::from_digit(10)); + /// assert_eq!(Some(Char::SmallF), Char::from_digit(15)); + /// assert_eq!(Some(Char::SmallZ), Char::from_digit(35)); + /// assert_eq!(None, Char::from_digit(36)); + /// ``` + #[unstable(feature = "ascii_char", issue = "110998")] + #[inline] + pub const fn from_digit(d: u32) -> Option { + const DIGITS: [AsciiChar; 36] = + *b"0123456789abcdefghijklmnopqrstuvwxyz".as_ascii().unwrap(); + if d < 36 { Some(DIGITS[d as usize]) } else { None } + } + /// Gets this ASCII character as a byte. #[unstable(feature = "ascii_char", issue = "110998")] #[inline] @@ -567,12 +599,14 @@ impl fmt::Display for AsciiChar { #[unstable(feature = "ascii_char", issue = "110998")] impl fmt::Debug for AsciiChar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use AsciiChar::*; + #[inline] - fn backslash(a: AsciiChar) -> ([AsciiChar; 4], u8) { - ([AsciiChar::ReverseSolidus, a, AsciiChar::Null, AsciiChar::Null], 2) + fn backslash(ch: AsciiChar) -> ([AsciiChar; 6], usize) { + ([Apostrophe, ReverseSolidus, ch, Apostrophe, Null, Null], 4) } - let (buf, len) = match self { + let (buf, len) = match *self { AsciiChar::Null => backslash(AsciiChar::Digit0), AsciiChar::CharacterTabulation => backslash(AsciiChar::SmallT), AsciiChar::CarriageReturn => backslash(AsciiChar::SmallR), @@ -582,21 +616,15 @@ impl fmt::Debug for AsciiChar { _ => { let byte = self.to_u8(); if !byte.is_ascii_control() { - ([*self, AsciiChar::Null, AsciiChar::Null, AsciiChar::Null], 1) + ([Apostrophe, *self, Apostrophe, Null, Null, Null], 3) } else { - const HEX_DIGITS: [AsciiChar; 16] = *b"0123456789abcdef".as_ascii().unwrap(); - - let hi = HEX_DIGITS[usize::from(byte >> 4)]; - let lo = HEX_DIGITS[usize::from(byte & 0xf)]; - ([AsciiChar::ReverseSolidus, AsciiChar::SmallX, hi, lo], 4) + let hi = Self::from_digit(u32::from(byte >> 4)).unwrap(); + let lo = Self::from_digit(u32::from(byte & 0xf)).unwrap(); + ([Apostrophe, ReverseSolidus, SmallX, hi, lo, Apostrophe], 6) } } }; - f.write_char('\'')?; - for byte in &buf[..len as usize] { - f.write_str(byte.as_str())?; - } - f.write_char('\'') + f.write_str(buf[..len].as_str()) } } diff --git a/library/core/src/char/convert.rs b/library/core/src/char/convert.rs index 453de9754be56..fddfba4e8212d 100644 --- a/library/core/src/char/convert.rs +++ b/library/core/src/char/convert.rs @@ -275,11 +275,13 @@ impl fmt::Display for CharTryFromError { pub(super) const fn from_digit(num: u32, radix: u32) -> Option { if radix > 36 { panic!("from_digit: radix is too high (maximum 36)"); - } - if num < radix { - let num = num as u8; - if num < 10 { Some((b'0' + num) as char) } else { Some((b'a' + num - 10) as char) } - } else { + } else if num >= radix { None + } else if let Some(chr) = crate::ascii::Char::from_digit(num) { + Some(chr.to_char()) + } else { + // SAFETY: We’ve verified `num ≤ radix ≤ 36` and for those values + // ascii::Char::from_digit always returns Some. + unsafe { crate::hint::unreachable_unchecked() } } } diff --git a/library/core/tests/ascii.rs b/library/core/tests/ascii.rs index f5f2dd0477885..a7d47458e9a84 100644 --- a/library/core/tests/ascii.rs +++ b/library/core/tests/ascii.rs @@ -479,3 +479,41 @@ fn ascii_ctype_const() { is_ascii_control => [false, false, false, false, false]; } } + +#[test] +fn test_ascii_from_digit() { + use core::ascii::Char; + + for d in 0..10 { + let want = Char::from_u8(b'0' + d).unwrap(); + assert_eq!(Some(want), Char::digit(d)); + assert_eq!(Some(want), Char::from_digit(d.into())); + } + + for d in 10..36 { + let want = Char::from_u8(b'a' + d - 10).unwrap(); + assert_eq!(None, Char::digit(d)); + assert_eq!(Some(want), Char::from_digit(d.into())); + } + + for d in 36..=255 { + assert_eq!(None, Char::digit(d)); + assert_eq!(None, Char::from_digit(d.into())); + } +} + +#[test] +fn test_ascii_char_fmt() { + use core::ascii::Char; + + #[track_caller] + fn check(want_display: &str, want_debug: &str, ch: Char) { + assert_eq!(want_display, format!("{ch}")); + assert_eq!(want_debug, format!("{ch:?}")); + } + + check("\0", "'\\0'", Char::Null); + check("\n", "'\\n'", Char::LineFeed); + check("\x07", "'\\x07'", Char::Bell); + check("a", "'a'", Char::SmallA); +}