from_ascii/
num.rs

1use std::error::{Error};
2use std::fmt;
3
4use base::{FromAscii};
5
6/// An error returned when parsing a numeric value from a ascii string fails.
7#[derive(Debug, Clone, PartialEq)]
8pub struct ParseIntError {
9    kind: IntErrorKind
10}
11
12#[derive(Debug, Clone, PartialEq)]
13enum IntErrorKind {
14    Empty,
15    InvalidDigit(u8),
16    Overflow,
17    Underflow,
18    InvalidRadix(u8),
19}
20
21impl IntErrorKind {
22    fn description(&self) -> &str {
23        match self {
24            &IntErrorKind::Empty => "cannot parse integer from empty string",
25            &IntErrorKind::InvalidDigit(_) => "invalid digit found in string",
26            &IntErrorKind::Overflow => "number too large to fit in target type",
27            &IntErrorKind::Underflow => "number too small to fit in target type",
28            &IntErrorKind::InvalidRadix(_) => "radix should be in 2..36 range",
29        }
30    }
31}
32
33impl fmt::Display for ParseIntError {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        self.kind.description().fmt(f)
36    }
37}
38
39impl Error for ParseIntError {
40    fn description(&self) -> &str {
41        self.kind.description()
42    }
43}
44
45#[inline]
46pub fn dec_to_digit(c: u8, radix: u8) -> Option<u8> {
47    let val = match c {
48        b'0' ... b'9' => c - b'0',
49        b'a' ... b'z' => c - b'a' + 10,
50        b'A' ... b'Z' => c - b'A' + 10,
51        _ => return None,
52    };
53    if val < radix {
54        Some(val)
55    } else {
56        None
57    }
58}
59
60trait FromAsciiHelper: {
61    fn signed() -> bool;
62}
63
64/// A trait to abstract the idea of creating a new instance of a numeric type from a
65/// ascii string with given radix.
66///
67/// It's a near clone of standard `FromStr` trait.
68pub trait FromAsciiRadix: Sized {
69    /// The associated error which can be returned from parsing.
70    type Err;
71
72    /// Parses a ascii string `s` to return a value of this type.
73    ///
74    /// If parsing succeeds, return the value inside `Ok`, otherwise
75    /// when the string is ill-formatted return an error specific to the
76    /// inside `Err`. The error type is specific to implementation of the trait.
77    ///
78    /// # Examples
79    ///
80    /// Basic usage with `i32`, a type that implements `FromAsciiRadix`:
81    ///
82    /// ```
83    /// use from_ascii::FromAsciiRadix;
84    ///
85    /// let s = b"FA";
86    /// let x = i32::from_ascii_radix(s, 16).unwrap();
87    ///
88    /// assert_eq!(250, x);
89    /// ```
90    fn from_ascii_radix(s: &[u8], radix: u8) -> Result<Self, Self::Err>;
91}
92
93macro_rules! implement {
94    ($t:ty, $signed: expr) => {
95        impl FromAsciiHelper for $t {
96            #[inline]
97            fn signed() -> bool { $signed }
98        }
99
100        impl FromAsciiRadix for $t {
101            type Err = ParseIntError;
102
103            #[inline]
104            fn from_ascii_radix(src: &[u8], radix: u8) -> Result<Self, Self::Err> {
105                if radix < 2 || radix > 36 {
106                    return Err(ParseIntError { kind: IntErrorKind::InvalidRadix(radix) })
107                }
108
109                let (is_positive, digits) = match src.get(0) {
110                    Some(&b'+') => (true, &src[1..]),
111                    Some(&b'-') if <$t>::signed() => (false, &src[1..]),
112                    Some(_) => (true, src),
113                    None => return Err(ParseIntError { kind: IntErrorKind::Empty }),
114                };
115
116                if digits.is_empty() {
117                    return Err(ParseIntError { kind: IntErrorKind::Empty });
118                }
119
120                let mut result: $t = 0;
121
122                if is_positive {
123                    for &c in digits {
124                        let x = match dec_to_digit(c, radix) {
125                            Some(x) => x as $t,
126                            None => return Err(ParseIntError { kind: IntErrorKind::InvalidDigit(c) } ),
127                        };
128                        result = match result.checked_mul(radix as $t) {
129                            Some(result) => result,
130                            None => return Err(ParseIntError { kind: IntErrorKind::Overflow }),
131                        };
132                        result = match result.checked_add(x) {
133                            Some(result) => result,
134                            None => return Err(ParseIntError { kind: IntErrorKind::Overflow }),
135                        };
136                    }
137                } else {
138                    for &c in digits {
139                        let x = match dec_to_digit(c, radix) {
140                            Some(x) => x as $t,
141                            None => return Err(ParseIntError { kind: IntErrorKind::InvalidDigit(c) }),
142                        };
143                        result = match result.checked_mul(radix as $t) {
144                            Some(result) => result,
145                            None => return Err(ParseIntError { kind: IntErrorKind::Underflow }),
146                        };
147                        result = match result.checked_sub(x) {
148                            Some(result) => result,
149                            None => return Err(ParseIntError { kind: IntErrorKind::Underflow }),
150                        };
151                    }
152                }
153                Ok(result)
154            }
155        }
156
157        impl FromAscii for $t {
158            type Err = ParseIntError;
159
160            #[inline]
161            fn from_ascii(src: &[u8]) -> Result<Self, Self::Err> {
162                <$t>::from_ascii_radix(src, 10)
163            }
164        }
165    }
166}
167
168implement!(i8, true);
169implement!(i16, true);
170implement!(i32, true);
171implement!(i64, true);
172implement!(isize, true);
173implement!(u8, false);
174implement!(u16, false);
175implement!(u32, false);
176implement!(u64, false);
177implement!(usize, false);
178
179#[cfg(test)]
180mod tests {
181    use super::super::base::FromAscii;
182    use super::FromAsciiRadix;
183
184    #[test]
185    fn test_from_ascii() {
186        assert_eq!(i8::from_ascii(b"10"), Ok(10));
187        assert_eq!(u8::from_ascii(b"10"), Ok(10));
188        assert_eq!(i64::from_ascii(b"10"), Ok(10));
189        assert_eq!(u64::from_ascii(b"10"), Ok(10));
190
191        assert_eq!(i8::from_ascii(b"-10"), Ok(-10));
192        assert_eq!(u8::from_ascii(b"-10").is_err(), true);
193        assert_eq!(i64::from_ascii(b"-10"), Ok(-10));
194        assert_eq!(u64::from_ascii(b"-10").is_err(), true);
195
196        assert_eq!(i8::from_ascii(b"1000").is_err(), true);
197        assert_eq!(u8::from_ascii(b"1000").is_err(), true);
198        assert_eq!(i64::from_ascii(b"1000"), Ok(1000));
199        assert_eq!(u64::from_ascii(b"1000"), Ok(1000));
200
201        assert_eq!(i8::from_ascii(b"-1000").is_err(), true);
202        assert_eq!(u8::from_ascii(b"-1000").is_err(), true);
203        assert_eq!(i64::from_ascii(b"-1000"), Ok(-1000));
204        assert_eq!(u64::from_ascii(b"-1000").is_err(), true);
205    }
206
207    #[test]
208    fn test_from_ascii_radix() {
209        assert_eq!(i8::from_ascii_radix(b"ff", 16).is_err(), true);
210        assert_eq!(u8::from_ascii_radix(b"ff", 16), Ok(255));
211        assert_eq!(i8::from_ascii_radix(b"FF", 16).is_err(), true);
212        assert_eq!(u8::from_ascii_radix(b"FF", 16), Ok(255));
213    }
214}