Skip to content

Commit

Permalink
core: add core::ascii::Char::from_digit
Browse files Browse the repository at this point in the history
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: rust-lang/libs-team#179
Issue: rust-lang#110998
  • Loading branch information
mina86 committed Dec 15, 2023
1 parent 03515c6 commit 781b460
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 21 deletions.
64 changes: 48 additions & 16 deletions library/core/src/ascii/ascii_char.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Self> {
Expand Down Expand Up @@ -515,6 +516,41 @@ 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<Self> {
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]
Expand Down Expand Up @@ -567,12 +603,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),
Expand All @@ -582,21 +620,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())
}
}
12 changes: 7 additions & 5 deletions library/core/src/char/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,13 @@ impl fmt::Display for CharTryFromError {
pub(super) const fn from_digit(num: u32, radix: u32) -> Option<char> {
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() }
}
}
38 changes: 38 additions & 0 deletions library/core/tests/ascii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit 781b460

Please sign in to comment.