cluna/
number.rs

1use crate::lexer::Lexer;
2
3#[derive(Debug)]
4pub struct HexDecimal {
5    before_decimal: String,
6    after_decimal: String,
7}
8
9impl HexDecimal {
10    #[inline]
11    fn new(before_decimal: String, after_decimal: String) -> Self {
12        HexDecimal {
13            before_decimal,
14            after_decimal,
15        }
16    }
17
18    fn into_decimal(self) -> f64 {
19        let mut num = 0.0f64;
20
21        for (i, c) in (0i32..).zip(self.before_decimal.chars().rev()) {
22            let digit = c.to_digit(16).unwrap() as f64;
23            num += digit * 16f64.powi(i);
24        }
25
26        for (i, c) in (0i32..).zip(self.after_decimal.chars().skip(1)) {
27            let digit = c.to_digit(16).unwrap() as f64;
28            num += digit * 16f64.powi(-(i + 1));
29        }
30
31        num
32    }
33}
34
35#[derive(Debug)]
36pub struct Decimal {
37    before_decimal: String,
38    after_decimal: String,
39}
40
41#[inline]
42fn normalize_before_decimal(before_decimal: String) -> String {
43    if before_decimal.is_empty() {
44        "0".to_string()
45    } else {
46        before_decimal
47    }
48}
49
50#[inline]
51fn normalize_after_decimal(after_decimal: String) -> String {
52    if after_decimal == "." {
53        ".0".to_string()
54    } else {
55        after_decimal
56    }
57}
58
59impl Decimal {
60    #[inline]
61    fn new(before_decimal: String, after_decimal: String) -> Self {
62        Decimal {
63            before_decimal: normalize_before_decimal(before_decimal),
64            after_decimal: normalize_after_decimal(after_decimal),
65        }
66    }
67}
68
69#[derive(Debug)]
70pub enum Number {
71    Hex(String),
72    HexDecimal(HexDecimal),
73    HexScientific {
74        mantissa: HexDecimal,
75        exponent: String,
76    },
77    Decimal(Decimal),
78    Scientific {
79        mantissa: Decimal,
80        exponent: String,
81    },
82}
83
84impl Number {
85    pub fn from_source(lexer: &mut Lexer) -> Result<Number, String> {
86        let start = lexer.current;
87        let mut digit_encountered = false;
88        let mut decimal_encountered = false;
89        let mut is_scientific = false;
90        let mut is_hex = false;
91        let mut sign_encountered = false;
92
93        let mut before_decimal = String::with_capacity(4);
94        let mut after_decimal = String::with_capacity(4);
95        let mut exponent = String::new();
96
97        while let Some(c) = lexer.advance() {
98            match c {
99                '0'..='9' => {
100                    digit_encountered = true;
101                    if is_scientific {
102                        exponent.push(c);
103                    } else if decimal_encountered {
104                        after_decimal.push(c);
105                    } else {
106                        before_decimal.push(c);
107                    }
108                }
109                '.' => {
110                    if decimal_encountered {
111                        return Err(format!(
112                            "Error: Malformed number at {}:{}",
113                            lexer.line, lexer.column
114                        ));
115                    }
116
117                    decimal_encountered = true;
118                    if is_scientific {
119                        return Err(format!(
120                            "Error: Malformed number at {}:{}",
121                            lexer.line, lexer.column
122                        ));
123                    }
124
125                    if is_hex
126                        && !digit_encountered
127                        && !lexer.peek().map_or(false, |c| c.is_ascii_hexdigit())
128                    {
129                        return Err(format!(
130                            "Error: Malformed number at {}:{}",
131                            lexer.line, lexer.column
132                        ));
133                    }
134
135                    if !is_hex
136                        && !digit_encountered
137                        && !lexer.peek().map_or(false, |c| c.is_ascii_digit())
138                    {
139                        return Err(format!(
140                            "Error: Malformed number at {}:{}",
141                            lexer.line, lexer.column
142                        ));
143                    }
144                    after_decimal.push(c);
145                }
146                'x' | 'X' => {
147                    if !lexer.look_back().map_or(false, |c| c == '0')
148                        || is_scientific
149                        || is_hex
150                        || !lexer
151                            .peek()
152                            .map_or(false, |c| c.is_ascii_hexdigit() || c == '.')
153                    {
154                        return Err(format!(
155                            "Error: Malformed number at {}:{}",
156                            lexer.line, lexer.column
157                        ));
158                    }
159                    is_hex = true;
160                    digit_encountered = false;
161                }
162                'e' | 'E' => {
163                    if !is_scientific && is_hex {
164                        continue;
165                    }
166
167                    if !digit_encountered
168                        || !lexer
169                            .peek()
170                            .map_or(false, |c| c.is_ascii_digit() || c == '+' || c == '-')
171                        || is_scientific
172                    {
173                        return Err(format!(
174                            "Error: Malformed number at {}:{}",
175                            lexer.line, lexer.column
176                        ));
177                    } else {
178                        exponent.push(lexer.advance().unwrap());
179                    }
180                    is_scientific = true;
181                }
182                'p' | 'P' => {
183                    if !is_hex || !digit_encountered || is_scientific {
184                        return Err(format!(
185                            "Error: Malformed number at {}:{}",
186                            lexer.line, lexer.column
187                        ));
188                    }
189
190                    if lexer
191                        .peek()
192                        .map_or(false, |c| c == '+' || c == '-' || c.is_ascii_digit())
193                    {
194                        exponent.push(lexer.advance().unwrap());
195                    } else {
196                        return Err(format!(
197                            "Error: Malformed number at {}:{}",
198                            lexer.line, lexer.column
199                        ));
200                    }
201                    is_scientific = true;
202                }
203                'a'..='f' | 'A'..='F' => {
204                    if !is_hex || is_scientific {
205                        return Err(format!(
206                            "Error: Malformed number at {}:{}",
207                            lexer.line, lexer.column
208                        ));
209                    }
210                    if decimal_encountered {
211                        after_decimal.push(c);
212                    } else {
213                        before_decimal.push(c);
214                    }
215                    digit_encountered = true;
216                }
217                '+' | '-' => {
218                    if !is_scientific {
219                        lexer.go_back();
220                        break;
221                    }
222
223                    if sign_encountered {
224                        return Err(format!(
225                            "Error: Malformed number at {}:{}",
226                            lexer.line, lexer.column
227                        ));
228                    }
229                    sign_encountered = true;
230                    exponent.push(c);
231                }
232                _ => {
233                    lexer.go_back();
234                    break;
235                }
236            }
237        }
238
239        if is_scientific
240            && !(lexer.source[lexer.current - 1].is_ascii_digit()
241                || lexer.peek().map_or(false, |c| c.is_ascii_digit()))
242        {
243            return Err(format!(
244                "Error: Malformed number at {}:{}",
245                lexer.line, lexer.column
246            ));
247        }
248
249        if !is_hex && !digit_encountered {
250            return Err(format!(
251                "Error: Malformed number at {}:{}",
252                lexer.line, lexer.column
253            ));
254        }
255
256        let len = lexer.current - start;
257        let lexeme: String = lexer.source[lexer.current - len..lexer.current]
258            .iter()
259            .collect();
260
261        let number = if is_scientific {
262            if is_hex {
263                let mantissa = HexDecimal::new(before_decimal, after_decimal);
264                Number::HexScientific { mantissa, exponent }
265            } else {
266                let mantissa = Decimal::new(before_decimal, after_decimal);
267                Number::Scientific { mantissa, exponent }
268            }
269        } else if is_hex {
270            if decimal_encountered {
271                let hex_decimal = HexDecimal::new(before_decimal[1..].to_string(), after_decimal);
272                Number::HexDecimal(hex_decimal)
273            } else {
274                Number::Hex(lexeme)
275            }
276        } else if decimal_encountered {
277            let decimal = Decimal::new(before_decimal, after_decimal);
278            Number::Decimal(decimal)
279        } else {
280            Number::Decimal(Decimal::new(before_decimal, after_decimal))
281        };
282        Ok(number)
283    }
284
285    pub fn into_clue_number(self) -> String {
286        match self {
287            Number::Hex(hex) => hex,
288            Number::HexDecimal(hex) => {
289                let number = hex.into_decimal().to_string();
290                if number == "inf" {
291                    "1 / 0".to_owned()
292                } else if number == "-inf" {
293                    "-1 / 0".to_owned()
294                } else {
295                    number
296                }
297            }
298            Number::HexScientific { mantissa, exponent } => {
299                let number = mantissa.into_decimal();
300                let number = number * 2f64.powi(exponent.parse::<i32>().unwrap());
301                let number = number.to_string();
302
303                if number == "inf" {
304                    "1 / 0".to_owned()
305                } else if number == "-inf" {
306                    "-1 / 0".to_owned()
307                } else {
308                    number
309                }
310            }
311            Number::Decimal(Decimal {
312                before_decimal,
313                after_decimal,
314            }) => {
315                let mut s = String::with_capacity(4);
316
317                s.push_str(&before_decimal);
318                s.push_str(&after_decimal);
319                s
320            }
321            Number::Scientific { mantissa, exponent } => {
322                let mut s = String::with_capacity(4);
323
324                s.push_str(&mantissa.before_decimal);
325                s.push_str(&mantissa.after_decimal);
326                s.push('e');
327                s.push_str(&exponent);
328                s
329            }
330        }
331    }
332}