rust_fixed_point_decimal_core/
parser.rs

1// ---------------------------------------------------------------------------
2// Copyright:   (c) 2021 ff. Michael Amrhein (michael@adrhinum.de)
3// License:     This program is part of a larger application. For license
4//              details please read the file LICENSE.TXT provided together
5//              with the application.
6// ---------------------------------------------------------------------------
7// $Source: core/src/parser.rs $
8// $Revision: 2021-10-29T12:14:58+02:00 $
9
10use std::fmt::{Display, Formatter};
11
12use crate::powers_of_ten::checked_mul_pow_ten;
13
14/// An error which can be returned when parsing a decimal literal.
15///
16/// This error is used as the error type for the `FromStr` implementation of
17/// `Decimal<P>`.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum ParseDecimalError {
20    /// An empty string has been given as literal.
21    Empty,
22    /// The given string is not a valid decimal literal.
23    Invalid,
24    /// The given decimal literal has more fractional digits than specified by
25    /// the type parameter `P`.
26    PrecLimitExceeded,
27    /// The given decimal literal would exceed the maximum value representable
28    /// by the type.
29    MaxValueExceeded,
30}
31
32impl ParseDecimalError {
33    #[doc(hidden)]
34    pub fn _description(&self) -> &str {
35        match self {
36            ParseDecimalError::Empty => "Empty string.",
37            ParseDecimalError::Invalid => "Invalid decimal string literal.",
38            ParseDecimalError::PrecLimitExceeded => {
39                "Too many fractional digits."
40            }
41            ParseDecimalError::MaxValueExceeded => {
42                "Maximum representable value exceeded."
43            }
44        }
45    }
46}
47
48impl Display for ParseDecimalError {
49    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50        Display::fmt(self._description(), f)
51    }
52}
53
54impl std::error::Error for ParseDecimalError {}
55
56struct DecLitParts<'a> {
57    num_sign: &'a str,
58    int_part: &'a str,
59    frac_part: &'a str,
60    exp_sign: &'a str,
61    exp_part: &'a str,
62}
63
64/// Parse a Decimal literal in the form
65/// \[+|-]<int>\[.<frac>]\[<e|E>\[+|-]<exp>] or
66/// \[+|-].<frac>\[<e|E>\[+|-]<exp>].
67fn parse_decimal_literal(lit: &str) -> Result<DecLitParts, ParseDecimalError> {
68    let mut num_sign_range = 0usize..0usize;
69    let mut int_part_range = 0usize..0usize;
70    let mut frac_part_range = 0usize..0usize;
71    let mut exp_sign_range = 0usize..0usize;
72    let mut exp_part_range = 0usize..0usize;
73    let mut chars = lit.char_indices();
74    let mut next = chars.next();
75    if let None = next {
76        return Result::Err(ParseDecimalError::Empty);
77    }
78    let (mut curr_idx, mut curr_char) = next.unwrap();
79    if curr_char == '-' || curr_char == '+' {
80        num_sign_range = curr_idx..curr_idx + 1;
81        next = chars.next();
82    }
83    int_part_range.start = num_sign_range.end;
84    loop {
85        match next {
86            None => {
87                curr_idx = lit.len();
88                if int_part_range.start < curr_idx {
89                    int_part_range.end = lit.len();
90                    return Ok(DecLitParts {
91                        num_sign: &lit[num_sign_range],
92                        int_part: &lit[int_part_range],
93                        frac_part: "",
94                        exp_sign: "",
95                        exp_part: "",
96                    });
97                } else {
98                    return Result::Err(ParseDecimalError::Invalid);
99                }
100            }
101            Some((idx, ch)) => {
102                if !ch.is_digit(10) {
103                    int_part_range.end = idx;
104                    curr_char = ch;
105                    curr_idx = idx;
106                    break;
107                }
108            }
109        }
110        next = chars.next();
111    }
112    if curr_char == '.' {
113        frac_part_range.start = curr_idx + 1;
114        next = chars.next();
115        loop {
116            match next {
117                None => {
118                    frac_part_range.end = lit.len();
119                    return Ok(DecLitParts {
120                        num_sign: &lit[num_sign_range],
121                        int_part: &lit[int_part_range],
122                        frac_part: &lit[frac_part_range],
123                        exp_sign: "",
124                        exp_part: "",
125                    });
126                }
127                Some((idx, ch)) => {
128                    if !ch.is_digit(10) {
129                        frac_part_range.end = idx;
130                        curr_char = ch;
131                        break;
132                    }
133                }
134            }
135            next = chars.next();
136        }
137    }
138    if curr_char == 'e' || curr_char == 'E' {
139        next = chars.next();
140        if let None = next {
141            return Result::Err(ParseDecimalError::Invalid);
142        }
143        let (curr_idx, curr_char) = next.unwrap();
144        if curr_char == '-' || curr_char == '+' {
145            exp_sign_range = curr_idx..curr_idx + 1;
146            exp_part_range.start = curr_idx + 1;
147        } else if curr_char.is_digit(10) {
148            exp_part_range.start = curr_idx;
149        } else {
150            return Result::Err(ParseDecimalError::Invalid);
151        }
152        next = chars.next();
153        loop {
154            match next {
155                None => {
156                    exp_part_range.end = lit.len();
157                    break;
158                }
159                Some((_, ch)) => {
160                    if !ch.is_digit(10) {
161                        return Result::Err(ParseDecimalError::Invalid);
162                    }
163                }
164            }
165            next = chars.next();
166        }
167    } else {
168        return Result::Err(ParseDecimalError::Invalid);
169    }
170    if int_part_range.len() == 0 && frac_part_range.len() == 0 {
171        return Result::Err(ParseDecimalError::Invalid);
172    }
173    Ok(DecLitParts {
174        num_sign: &lit[num_sign_range],
175        int_part: &lit[int_part_range],
176        frac_part: &lit[frac_part_range],
177        exp_sign: &lit[exp_sign_range],
178        exp_part: &lit[exp_part_range],
179    })
180}
181
182/// Convert a decimal number literal into a representation in the form
183/// (coefficient, exponent), so that number == coefficient * 10 ^ exponent.
184///
185/// The literal must be in the form
186/// \[+|-]<int>\[.<frac>]\[<e|E>\[+|-]<exp>] or
187/// \[+|-].<frac>\[<e|E>\[+|-]<exp>].
188#[doc(hidden)]
189pub fn dec_repr_from_str(
190    lit: &str,
191) -> Result<(i128, isize), ParseDecimalError> {
192    let max_prec = crate::MAX_PREC as isize;
193    let parts = parse_decimal_literal(lit)?;
194    let exp: isize = if parts.exp_part.len() > 0 {
195        if parts.exp_sign == "-" {
196            -parts.exp_part.parse::<isize>().unwrap()
197        } else {
198            parts.exp_part.parse().unwrap()
199        }
200    } else {
201        0
202    };
203    let n_frac_digits = parts.frac_part.len() as isize;
204    if n_frac_digits - exp > max_prec {
205        return Result::Err(ParseDecimalError::PrecLimitExceeded);
206    }
207    let mut coeff: i128 = if parts.int_part.len() > 0 {
208        match parts.int_part.parse() {
209            Err(_) => {
210                return Err(ParseDecimalError::MaxValueExceeded);
211            }
212            Ok(i) => i,
213        }
214    } else {
215        0
216    };
217    if n_frac_digits > 0 {
218        match checked_mul_pow_ten(coeff, n_frac_digits as u8) {
219            None => return Result::Err(ParseDecimalError::MaxValueExceeded),
220            Some(val) => coeff = val,
221        }
222        coeff += parts.frac_part.parse::<i128>().unwrap();
223    }
224    if parts.num_sign == "-" {
225        Ok((-coeff, exp - n_frac_digits))
226    } else {
227        Ok((coeff, exp - n_frac_digits))
228    }
229}