fe_common/
numeric.rs

1use num_bigint::{BigInt, Sign};
2
3/// A type that represents the radix of a numeric literal.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Radix {
6    Hexadecimal,
7    Decimal,
8    Octal,
9    Binary,
10}
11
12impl Radix {
13    /// Returns number representation of the radix.
14    pub fn as_num(self) -> u32 {
15        match self {
16            Self::Hexadecimal => 16,
17            Self::Decimal => 10,
18            Self::Octal => 8,
19            Self::Binary => 2,
20        }
21    }
22}
23
24/// A helper type to interpret a numeric literal represented by string.
25#[derive(Debug, Clone)]
26pub struct Literal<'a> {
27    /// The number part of the string.
28    num: &'a str,
29    /// The radix of the literal.
30    radix: Radix,
31}
32
33impl<'a> Literal<'a> {
34    pub fn new(src: &'a str) -> Self {
35        debug_assert!(!src.is_empty());
36        debug_assert_ne!(src.chars().next(), Some('-'));
37        let (radix, prefix) = if src.len() < 2 {
38            (Radix::Decimal, None)
39        } else {
40            match &src[0..2] {
41                "0x" | "0X" => (Radix::Hexadecimal, Some(&src[..2])),
42                "0o" | "0O" => (Radix::Octal, Some(&src[..2])),
43                "0b" | "0B" => (Radix::Binary, Some(&src[..2])),
44                _ => (Radix::Decimal, None),
45            }
46        };
47
48        Self {
49            num: &src[prefix.map_or(0, str::len)..],
50            radix,
51        }
52    }
53
54    /// Parse the numeric literal to `T`.
55    pub fn parse<T: num_traits::Num>(&self) -> Result<T, T::FromStrRadixErr> {
56        T::from_str_radix(self.num, self.radix.as_num())
57    }
58
59    /// Returns radix of the numeric literal.
60    pub fn radix(&self) -> Radix {
61        self.radix
62    }
63}
64
65// Converts any positive or negative `BigInt` into a hex str using 2s complement representation for negative values.
66pub fn to_hex_str(val: &BigInt) -> String {
67    format!(
68        "0x{}",
69        BigInt::from_bytes_be(Sign::Plus, &val.to_signed_bytes_be()).to_str_radix(16)
70    )
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn test_radix() {
79        assert_eq!(Literal::new("0XFF").radix(), Radix::Hexadecimal);
80        assert_eq!(Literal::new("0xFF").radix(), Radix::Hexadecimal);
81        assert_eq!(Literal::new("0O77").radix(), Radix::Octal);
82        assert_eq!(Literal::new("0o77").radix(), Radix::Octal);
83        assert_eq!(Literal::new("0B77").radix(), Radix::Binary);
84        assert_eq!(Literal::new("0b77").radix(), Radix::Binary);
85        assert_eq!(Literal::new("1").radix(), Radix::Decimal);
86
87        // Invalid radix is treated as `Decimal`.
88        assert_eq!(Literal::new("0D15").radix(), Radix::Decimal);
89    }
90
91    #[test]
92    fn test_to_hex_str() {
93        assert_eq!(to_hex_str(&BigInt::from(-1i8)), "0xff");
94        assert_eq!(to_hex_str(&BigInt::from(-2i8)), "0xfe");
95        assert_eq!(to_hex_str(&BigInt::from(1i8)), "0x1");
96        assert_eq!(to_hex_str(&BigInt::from(2i8)), "0x2");
97    }
98}