Skip to content

Commit 781b460

Browse files
committed
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: rust-lang/libs-team#179 Issue: #110998
1 parent 03515c6 commit 781b460

File tree

3 files changed

+93
-21
lines changed

3 files changed

+93
-21
lines changed

library/core/src/ascii/ascii_char.rs

+48-16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! suggestions from rustc if you get anything slightly wrong in here, and overall
44
//! helps with clarity as we're also referring to `char` intentionally in here.
55
6-
use crate::fmt::{self, Write};
6+
use crate::fmt;
77
use crate::mem::transmute;
88

99
/// One of the 128 Unicode characters from U+0000 through U+007F,
@@ -474,7 +474,8 @@ impl AsciiChar {
474474
/// When passed the *number* `0`, `1`, …, `9`, returns the *character*
475475
/// `'0'`, `'1'`, …, `'9'` respectively.
476476
///
477-
/// If `d >= 10`, returns `None`.
477+
/// If `d >= 10`, returns `None`. To get a digit up to `d == 35`, use
478+
/// [`from_digit`](Self::from_digit) instead.
478479
#[unstable(feature = "ascii_char", issue = "110998")]
479480
#[inline]
480481
pub const fn digit(d: u8) -> Option<Self> {
@@ -515,6 +516,41 @@ impl AsciiChar {
515516
}
516517
}
517518

519+
/// Returns a *character* digit corresponding to given numeric value of
520+
/// a digit.
521+
///
522+
/// When passed the *number* `0`, `1`, …, `8`, `9`, `10`, …, `34`, `35`,
523+
/// returns the *character* `'0'`, `'1'`, …, `'8'`, `'9'`, `'a'`, …, `'y'`,
524+
/// `'z'` respectively.
525+
///
526+
/// If `d >= 36`, returns `None`. To get a digit only for `d < 10`, use
527+
/// [`digit`](Self::digit) instead.
528+
///
529+
/// # Example
530+
///
531+
/// ```
532+
/// #![feature(ascii_char, ascii_char_variants)]
533+
/// use core::ascii::Char;
534+
///
535+
/// assert_eq!(Some(Char::Digit0), Char::from_digit(0));
536+
/// assert_eq!(Some(Char::Digit9), Char::from_digit(9));
537+
/// assert_eq!(Some(Char::SmallA), Char::from_digit(10));
538+
/// assert_eq!(Some(Char::SmallF), Char::from_digit(15));
539+
/// assert_eq!(Some(Char::SmallZ), Char::from_digit(35));
540+
/// assert_eq!(None, Char::from_digit(36));
541+
/// ```
542+
#[unstable(feature = "ascii_char", issue = "110998")]
543+
#[inline]
544+
pub const fn from_digit(d: u32) -> Option<Self> {
545+
const DIGITS: [AsciiChar; 36] =
546+
*b"0123456789abcdefghijklmnopqrstuvwxyz".as_ascii().unwrap();
547+
if d < 36 {
548+
Some(DIGITS[d as usize])
549+
} else {
550+
None
551+
}
552+
}
553+
518554
/// Gets this ASCII character as a byte.
519555
#[unstable(feature = "ascii_char", issue = "110998")]
520556
#[inline]
@@ -567,12 +603,14 @@ impl fmt::Display for AsciiChar {
567603
#[unstable(feature = "ascii_char", issue = "110998")]
568604
impl fmt::Debug for AsciiChar {
569605
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606+
use AsciiChar::*;
607+
570608
#[inline]
571-
fn backslash(a: AsciiChar) -> ([AsciiChar; 4], u8) {
572-
([AsciiChar::ReverseSolidus, a, AsciiChar::Null, AsciiChar::Null], 2)
609+
fn backslash(ch: AsciiChar) -> ([AsciiChar; 6], usize) {
610+
([Apostrophe, ReverseSolidus, ch, Apostrophe, Null, Null], 4)
573611
}
574612

575-
let (buf, len) = match self {
613+
let (buf, len) = match *self {
576614
AsciiChar::Null => backslash(AsciiChar::Digit0),
577615
AsciiChar::CharacterTabulation => backslash(AsciiChar::SmallT),
578616
AsciiChar::CarriageReturn => backslash(AsciiChar::SmallR),
@@ -582,21 +620,15 @@ impl fmt::Debug for AsciiChar {
582620
_ => {
583621
let byte = self.to_u8();
584622
if !byte.is_ascii_control() {
585-
([*self, AsciiChar::Null, AsciiChar::Null, AsciiChar::Null], 1)
623+
([Apostrophe, *self, Apostrophe, Null, Null, Null], 3)
586624
} else {
587-
const HEX_DIGITS: [AsciiChar; 16] = *b"0123456789abcdef".as_ascii().unwrap();
588-
589-
let hi = HEX_DIGITS[usize::from(byte >> 4)];
590-
let lo = HEX_DIGITS[usize::from(byte & 0xf)];
591-
([AsciiChar::ReverseSolidus, AsciiChar::SmallX, hi, lo], 4)
625+
let hi = Self::from_digit(u32::from(byte >> 4)).unwrap();
626+
let lo = Self::from_digit(u32::from(byte & 0xf)).unwrap();
627+
([Apostrophe, ReverseSolidus, SmallX, hi, lo, Apostrophe], 6)
592628
}
593629
}
594630
};
595631

596-
f.write_char('\'')?;
597-
for byte in &buf[..len as usize] {
598-
f.write_str(byte.as_str())?;
599-
}
600-
f.write_char('\'')
632+
f.write_str(buf[..len].as_str())
601633
}
602634
}

library/core/src/char/convert.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,13 @@ impl fmt::Display for CharTryFromError {
275275
pub(super) const fn from_digit(num: u32, radix: u32) -> Option<char> {
276276
if radix > 36 {
277277
panic!("from_digit: radix is too high (maximum 36)");
278-
}
279-
if num < radix {
280-
let num = num as u8;
281-
if num < 10 { Some((b'0' + num) as char) } else { Some((b'a' + num - 10) as char) }
282-
} else {
278+
} else if num >= radix {
283279
None
280+
} else if let Some(chr) = crate::ascii::Char::from_digit(num) {
281+
Some(chr.to_char())
282+
} else {
283+
// SAFETY: We’ve verified `num ≤ radix ≤ 36` and for those values
284+
// ascii::Char::from_digit always returns Some.
285+
unsafe { crate::hint::unreachable_unchecked() }
284286
}
285287
}

library/core/tests/ascii.rs

+38
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,41 @@ fn ascii_ctype_const() {
479479
is_ascii_control => [false, false, false, false, false];
480480
}
481481
}
482+
483+
#[test]
484+
fn test_ascii_from_digit() {
485+
use core::ascii::Char;
486+
487+
for d in 0..10 {
488+
let want = Char::from_u8(b'0' + d).unwrap();
489+
assert_eq!(Some(want), Char::digit(d));
490+
assert_eq!(Some(want), Char::from_digit(d.into()));
491+
}
492+
493+
for d in 10..36 {
494+
let want = Char::from_u8(b'a' + d - 10).unwrap();
495+
assert_eq!(None, Char::digit(d));
496+
assert_eq!(Some(want), Char::from_digit(d.into()));
497+
}
498+
499+
for d in 36..=255 {
500+
assert_eq!(None, Char::digit(d));
501+
assert_eq!(None, Char::from_digit(d.into()));
502+
}
503+
}
504+
505+
#[test]
506+
fn test_ascii_char_fmt() {
507+
use core::ascii::Char;
508+
509+
#[track_caller]
510+
fn check(want_display: &str, want_debug: &str, ch: Char) {
511+
assert_eq!(want_display, format!("{ch}"));
512+
assert_eq!(want_debug, format!("{ch:?}"));
513+
}
514+
515+
check("\0", "'\\0'", Char::Null);
516+
check("\n", "'\\n'", Char::LineFeed);
517+
check("\x07", "'\\x07'", Char::Bell);
518+
check("a", "'a'", Char::SmallA);
519+
}

0 commit comments

Comments
 (0)