1use super::derive_ASTNode;
2use std::{borrow::Cow, str::FromStr};
3
4#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
5#[apply(derive_ASTNode)]
6pub enum NumberSign {
7 Positive,
9 Negative,
10}
11
12impl NumberSign {
13 pub fn apply<T: std::ops::Neg<Output = T>>(&self, x: T) -> T {
14 match self {
15 NumberSign::Positive => x,
16 NumberSign::Negative => -x,
17 }
18 }
19}
20
21impl std::ops::Neg for NumberSign {
22 type Output = Self;
23
24 fn neg(self) -> Self::Output {
25 match self {
26 NumberSign::Positive => NumberSign::Negative,
27 NumberSign::Negative => NumberSign::Positive,
28 }
29 }
30}
31
32impl std::fmt::Display for NumberSign {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 if matches!(self, Self::Negative) {
35 f.write_str("-")
36 } else {
37 Ok(())
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
46#[apply(derive_ASTNode)]
47pub enum NumberRepresentation {
48 Infinity,
49 NegativeInfinity,
50 NaN,
51 Hex { sign: NumberSign, value: u64 },
52 Bin { sign: NumberSign, value: u64 },
53 Octal { sign: NumberSign, value: u64 },
54 Number(f64),
55 Exponential { sign: NumberSign, value: f64, exponent: i32 },
56 BigInt(NumberSign, String),
57}
58
59impl std::hash::Hash for NumberRepresentation {
60 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
61 core::mem::discriminant(self).hash(state);
62 }
63}
64
65impl TryFrom<NumberRepresentation> for f64 {
66 type Error = ();
68
69 fn try_from(this: NumberRepresentation) -> Result<Self, Self::Error> {
70 match this {
71 NumberRepresentation::Infinity => Ok(f64::INFINITY),
72 NumberRepresentation::NegativeInfinity => Ok(f64::NEG_INFINITY),
73 NumberRepresentation::NaN => Ok(f64::NAN),
74 NumberRepresentation::Number(value) => Ok(value),
75 NumberRepresentation::Hex { sign, value, .. }
76 | NumberRepresentation::Bin { sign, value, .. }
77 | NumberRepresentation::Octal { sign, value, .. } => {
78 #[allow(clippy::cast_precision_loss)]
80 Ok(sign.apply(value as f64))
81 }
82 NumberRepresentation::Exponential { sign, value, exponent } => {
83 Ok(sign.apply(value * 10f64.powi(exponent)))
84 }
85 NumberRepresentation::BigInt(..) => Err(()),
86 }
87 }
88}
89
90impl From<f64> for NumberRepresentation {
92 fn from(value: f64) -> Self {
93 if value == f64::INFINITY {
94 Self::Infinity
95 } else if value == f64::NEG_INFINITY {
96 Self::NegativeInfinity
97 } else if value.is_nan() {
98 Self::NaN
99 } else {
100 Self::Number(value)
101 }
102 }
103}
104
105impl FromStr for NumberRepresentation {
106 type Err = String;
107
108 fn from_str(s: &str) -> Result<Self, Self::Err> {
109 if s == "NaN" {
110 return Ok(Self::NaN);
111 }
112
113 if s.contains('_') {
114 return s.replace('_', "").parse();
115 }
116
117 let (sign, s) = if let Some(s) = s.strip_prefix('-') {
118 (NumberSign::Negative, s)
119 } else {
120 (NumberSign::Positive, s)
121 };
122
123 let s = if s.contains('_') { Cow::Owned(s.replace('_', "")) } else { Cow::Borrowed(s) };
124
125 if let Some(s) = s.strip_suffix('n') {
126 Ok(NumberRepresentation::BigInt(sign, s.to_owned()))
127 } else if let Some(s) = s.strip_prefix('0') {
128 let next_char = s.chars().next();
129 match next_char {
130 Some('.') => {
131 if s.len() == 1 {
132 Ok(Self::Number(0f64))
133 } else {
134 Ok(Self::Number(sign.apply(s.parse().map_err(|_| s.to_owned())?)))
135 }
136 }
137 Some('X' | 'x') => {
138 let mut value = 0u64;
139 for c in s[1..].as_bytes() {
140 value <<= 4; match c {
142 b'0'..=b'9' => {
143 value += u64::from(c - b'0');
144 }
145 b'a'..=b'f' => {
146 value += u64::from(c - b'a') + 10;
147 }
148 b'A'..=b'F' => {
149 value += u64::from(c - b'A') + 10;
150 }
151 _ => return Err(s.to_owned()),
152 }
153 }
154 Ok(Self::Hex { sign, value })
155 }
156 Some('B' | 'b') => {
157 let mut value = 0u64;
158 for c in s[1..].as_bytes() {
159 value <<= 1;
160 match c {
161 b'0' | b'1' => {
162 value += u64::from(c - b'0');
163 }
164 _ => return Err(s.to_owned()),
165 }
166 }
167 Ok(Self::Bin { sign, value })
168 }
169 Some('e' | 'E') => {
170 let exponent: i32 = s[1..].parse().map_err(|_| s.to_owned())?;
172 Ok(Self::Exponential { sign, value: 0f64, exponent })
173 }
174 Some(c) => {
176 let uses_character = matches!(c, 'o' | 'O');
177
178 if !uses_character && s.contains(['8', '9', '.']) {
179 return Ok(Self::Number(sign.apply(s.parse().map_err(|_| s.to_owned())?)));
180 }
181
182 let start: usize = uses_character.into();
184
185 let mut value = 0u64;
186 for c in s[start..].as_bytes() {
187 value <<= 3; if matches!(c, b'0'..=b'7') {
189 value += u64::from(c - b'0');
190 } else {
191 return Err(s.to_owned());
192 }
193 }
194 Ok(Self::Octal { sign, value })
195 }
196 None => Ok(Self::Number(0f64)),
197 }
198 } else if s.starts_with('.') {
199 let value: f64 = format!("0{s}").parse().map_err(|_| s.clone())?;
200 Ok(Self::Number(sign.apply(value)))
201 } else if let Some(s) = s.strip_suffix('.') {
202 Ok(Self::Number(sign.apply(s.parse::<f64>().map_err(|_| s)?)))
203 } else if let Some((left, right)) = s.split_once(['e', 'E']) {
204 let value = left.parse::<f64>().map_err(|_| s.clone())?;
205 if let Ok(exponent) = right.parse::<i32>() {
206 Ok(Self::Exponential { sign, value, exponent })
207 } else if right.starts_with('-') || value == 0f64 {
208 Ok(Self::Number(0f64))
210 } else {
211 Ok(Self::Infinity)
212 }
213 } else {
214 Ok(Self::Number(sign.apply(s.parse::<f64>().map_err(|_| s.clone())?)))
215 }
216 }
217}
218
219impl std::fmt::Display for NumberRepresentation {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 write!(f, "{}", self.clone().as_js_string())
222 }
223}
224
225impl PartialEq for NumberRepresentation {
227 fn eq(&self, other: &Self) -> bool {
228 if let (Ok(a), Ok(b)) = (f64::try_from(self.clone()), f64::try_from(other.clone())) {
229 a == b
230 } else {
231 false
233 }
234 }
235}
236
237impl Eq for NumberRepresentation {}
238
239impl std::ops::Neg for NumberRepresentation {
240 type Output = Self;
241
242 fn neg(self) -> Self::Output {
243 match self {
244 NumberRepresentation::Infinity => NumberRepresentation::NegativeInfinity,
245 NumberRepresentation::NegativeInfinity => NumberRepresentation::Infinity,
246 NumberRepresentation::NaN => NumberRepresentation::NaN,
247 NumberRepresentation::Hex { sign, value } => {
248 NumberRepresentation::Hex { sign: sign.neg(), value }
249 }
250 NumberRepresentation::Bin { sign, value } => {
251 NumberRepresentation::Bin { sign: sign.neg(), value }
252 }
253 NumberRepresentation::Octal { sign, value } => {
254 NumberRepresentation::Octal { sign: sign.neg(), value }
255 }
256 NumberRepresentation::Number(n) => NumberRepresentation::Number(n.neg()),
257 NumberRepresentation::Exponential { sign, value, exponent } => {
258 NumberRepresentation::Exponential { sign: sign.neg(), value, exponent }
259 }
260 NumberRepresentation::BigInt(sign, value) => {
261 NumberRepresentation::BigInt(sign.neg(), value)
262 }
263 }
264 }
265}
266
267impl NumberRepresentation {
268 #[must_use]
269 pub fn as_js_string(self) -> String {
270 match self {
271 NumberRepresentation::Infinity => "Infinity".to_owned(),
272 NumberRepresentation::NegativeInfinity => "-Infinity".to_owned(),
273 NumberRepresentation::NaN => "NaN".to_owned(),
274 NumberRepresentation::Hex { sign, value, .. } => {
275 format!("{sign}0x{value:x}")
276 }
277 NumberRepresentation::Bin { sign, value, .. } => {
278 format!("{sign}0b{value:b}")
279 }
280 NumberRepresentation::Octal { sign, value } => {
281 format!("{sign}0o{value:o}")
282 }
283 NumberRepresentation::Number(value) => value.to_string(),
284 NumberRepresentation::Exponential { sign, value, exponent } => {
285 format!("{sign}{value}e{exponent}")
286 }
287 NumberRepresentation::BigInt(s, value) => format!("{s}{value}n"),
288 }
289 }
290}