ezno_parser/
number.rs

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	/// Also implies non negative/missing
8	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/// Some of these can't be parsed, but are there to make so that a number expression can be generated from a f64
43///
44/// <https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-literals-numeric-literals>
45#[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	// BigInt!!
67	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				// TODO `value as f64` can lose information? If so should return f64::INFINITY
79				#[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
90// For code generation
91impl 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; // 16=2^4
141						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					// Lol
171					let exponent: i32 = s[1..].parse().map_err(|_| s.to_owned())?;
172					Ok(Self::Exponential { sign, value: 0f64, exponent })
173				}
174				// 'o' | 'O' but can also be missed
175				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					// If it uses the the character then skip one, else skip zero
183					let start: usize = uses_character.into();
184
185					let mut value = 0u64;
186					for c in s[start..].as_bytes() {
187						value <<= 3; // 8=2^3
188						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				// lol
209				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
225// TODO not great
226impl 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			// TODO ...
232			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}