1pub trait ToJsString {
3 fn to_js_string(&self) -> String;
4}
5
6impl ToJsString for f64 {
7 fn to_js_string(&self) -> String {
8 let mut buffer = dragonbox_ecma::Buffer::new();
9 buffer.format(*self).to_string()
10 }
11}
12
13pub fn parse_canonical_index(property_key: &str) -> Option<usize> {
18 let bytes = property_key.as_bytes();
19 let is_canonical = match bytes {
20 [b'0'] => true,
21 [b'1'..=b'9', rest @ ..] => rest.iter().all(u8::is_ascii_digit),
22 _ => false,
23 };
24
25 if !is_canonical {
26 return None;
27 }
28
29 property_key.parse().ok()
30}
31
32#[derive(Debug, Clone, Copy, PartialEq)]
33pub struct JsNumber(f64);
34
35impl From<f64> for JsNumber {
36 fn from(v: f64) -> Self {
37 JsNumber(v)
38 }
39}
40
41impl From<JsNumber> for f64 {
42 fn from(v: JsNumber) -> Self {
43 v.0
44 }
45}
46
47impl std::ops::Deref for JsNumber {
48 type Target = f64;
49
50 fn deref(&self) -> &Self::Target {
51 &self.0
52 }
53}
54
55impl JsNumber {
56 pub fn to_uint16(self) -> u16 {
60 self.as_uint32() as u16
61 }
62
63 fn as_int32(&self) -> i32 {
65 self.as_uint32() as i32
66 }
67
68 fn as_uint32(&self) -> u32 {
70 if !self.0.is_finite() {
71 return 0;
72 }
73
74 self.0.trunc().rem_euclid(4294967296.0) as u32
76 }
77}
78
79impl std::ops::Add<JsNumber> for JsNumber {
81 type Output = JsNumber;
82
83 fn add(self, rhs: JsNumber) -> Self::Output {
84 JsNumber(self.0 + rhs.0)
85 }
86}
87
88impl std::ops::Sub<JsNumber> for JsNumber {
90 type Output = JsNumber;
91
92 fn sub(self, rhs: JsNumber) -> Self::Output {
93 JsNumber(self.0 - rhs.0)
94 }
95}
96
97impl std::ops::Mul<JsNumber> for JsNumber {
99 type Output = JsNumber;
100
101 fn mul(self, rhs: JsNumber) -> Self::Output {
102 JsNumber(self.0 * rhs.0)
103 }
104}
105
106impl std::ops::Div<JsNumber> for JsNumber {
108 type Output = JsNumber;
109
110 fn div(self, rhs: JsNumber) -> Self::Output {
111 JsNumber(self.0 / rhs.0)
112 }
113}
114
115impl std::ops::Rem<JsNumber> for JsNumber {
117 type Output = JsNumber;
118
119 fn rem(self, rhs: JsNumber) -> Self::Output {
120 JsNumber(self.0 % rhs.0)
121 }
122}
123
124impl JsNumber {
126 pub fn pow(self, rhs: JsNumber) -> JsNumber {
127 if rhs.0.is_nan() {
130 return JsNumber(f64::NAN);
131 }
132
133 if self.0.abs() == 1f64 && rhs.0.is_infinite() {
134 return JsNumber(f64::NAN);
135 }
136
137 JsNumber(self.0.powf(rhs.0))
138 }
139}
140
141impl std::ops::Shl<JsNumber> for JsNumber {
144 type Output = JsNumber;
145
146 fn shl(self, rhs: JsNumber) -> Self::Output {
147 JsNumber(self.as_int32().wrapping_shl(rhs.as_uint32()) as f64)
148 }
149}
150
151impl std::ops::Shr<JsNumber> for JsNumber {
154 type Output = JsNumber;
155
156 fn shr(self, rhs: JsNumber) -> Self::Output {
157 JsNumber((self.as_int32()).wrapping_shr(rhs.as_uint32()) as f64)
158 }
159}
160
161impl JsNumber {
164 pub fn unsigned_shr(self, rhs: JsNumber) -> JsNumber {
165 JsNumber((self.as_uint32()).wrapping_shr(rhs.as_uint32()) as f64)
166 }
167}
168
169impl std::ops::BitOr<JsNumber> for JsNumber {
172 type Output = JsNumber;
173
174 fn bitor(self, rhs: JsNumber) -> Self::Output {
175 JsNumber((self.as_int32() | rhs.as_int32()) as f64)
176 }
177}
178
179impl std::ops::BitAnd<JsNumber> for JsNumber {
182 type Output = JsNumber;
183
184 fn bitand(self, rhs: JsNumber) -> Self::Output {
185 JsNumber((self.as_int32() & rhs.as_int32()) as f64)
186 }
187}
188
189impl std::ops::BitXor<JsNumber> for JsNumber {
192 type Output = JsNumber;
193
194 fn bitxor(self, rhs: JsNumber) -> Self::Output {
195 JsNumber((self.as_int32() ^ rhs.as_int32()) as f64)
196 }
197}
198
199impl std::ops::Neg for JsNumber {
201 type Output = JsNumber;
202
203 fn neg(self) -> Self::Output {
204 JsNumber(-self.0)
205 }
206}
207
208impl std::ops::Not for JsNumber {
210 type Output = JsNumber;
211
212 fn not(self) -> Self::Output {
213 JsNumber(!(self.as_int32()) as f64)
214 }
215}
216
217fn clz(s: &str) -> usize {
218 s.as_bytes().iter().take_while(|&&c| c == b'0').count()
219}
220
221pub fn minify_number(num: f64, detect_dot: &mut bool) -> String {
222 'hex: {
226 if num.fract() == 0.0 && num.abs() <= u64::MAX as f64 {
227 let int = num.abs() as u64;
228
229 if int < 10000000 {
230 break 'hex;
231 }
232
233 if int % 1000 == 0 {
235 break 'hex;
236 }
237
238 *detect_dot = false;
239 return format!(
240 "{}{:#x}",
241 if num.is_sign_negative() { "-" } else { "" },
242 int
243 );
244 }
245 }
246
247 let mut num = num.to_string();
248
249 if num.contains(".") {
250 *detect_dot = false;
251 }
252
253 if let Some(num) = num.strip_prefix("0.") {
254 let cnt = clz(num);
255 if cnt > 2 {
256 return format!("{}e-{}", &num[cnt..], num.len());
257 }
258 return format!(".{num}");
259 }
260
261 if let Some(num) = num.strip_prefix("-0.") {
262 let cnt = clz(num);
263 if cnt > 2 {
264 return format!("-{}e-{}", &num[cnt..], num.len());
265 }
266 return format!("-.{num}");
267 }
268
269 if num.ends_with("000") {
270 *detect_dot = false;
271
272 let cnt = num
273 .as_bytes()
274 .iter()
275 .rev()
276 .skip(3)
277 .take_while(|&&c| c == b'0')
278 .count()
279 + 3;
280
281 num.truncate(num.len() - cnt);
282 num.push('e');
283 num.push_str(&cnt.to_string());
284 }
285
286 num
287}
288
289#[cfg(test)]
290mod test_js_number {
291 use super::*;
292
293 #[test]
294 fn test_parse_canonical_index() {
295 assert_eq!(parse_canonical_index("0"), Some(0));
296 assert_eq!(parse_canonical_index("42"), Some(42));
297 assert_eq!(parse_canonical_index(""), None);
298 assert_eq!(parse_canonical_index("01"), None);
299 assert_eq!(parse_canonical_index("1.0"), None);
300 assert_eq!(parse_canonical_index("+1"), None);
301 assert_eq!(parse_canonical_index("-0"), None);
302 }
303
304 #[test]
305 fn test_as_int32() {
306 assert_eq!(JsNumber(f64::NAN).as_int32(), 0);
307 assert_eq!(JsNumber(0.0).as_int32(), 0);
308 assert_eq!(JsNumber(-0.0).as_int32(), 0);
309 assert_eq!(JsNumber(f64::INFINITY).as_int32(), 0);
310 assert_eq!(JsNumber(f64::NEG_INFINITY).as_int32(), 0);
311 }
312
313 #[test]
314 fn test_as_uint32() {
315 assert_eq!(JsNumber(f64::NAN).as_uint32(), 0);
316 assert_eq!(JsNumber(0.0).as_uint32(), 0);
317 assert_eq!(JsNumber(-0.0).as_uint32(), 0);
318 assert_eq!(JsNumber(f64::INFINITY).as_uint32(), 0);
319 assert_eq!(JsNumber(f64::NEG_INFINITY).as_uint32(), 0);
320 assert_eq!(JsNumber(-8.0).as_uint32(), 4294967288);
321 }
322
323 #[test]
324 fn test_to_uint16() {
325 assert_eq!(JsNumber(65.9).to_uint16(), 65);
326 assert_eq!(JsNumber(-1.0).to_uint16(), 65535);
327 assert_eq!(JsNumber(-65535.9).to_uint16(), 1);
328 assert_eq!(JsNumber(65536.0).to_uint16(), 0);
329 assert_eq!(JsNumber(65537.0).to_uint16(), 1);
330 assert_eq!(JsNumber(f64::NAN).to_uint16(), 0);
331 assert_eq!(JsNumber(f64::INFINITY).to_uint16(), 0);
332 assert_eq!(JsNumber(f64::NEG_INFINITY).to_uint16(), 0);
333 assert_eq!(JsNumber(4294967361.0).to_uint16(), 65);
334 assert_eq!(JsNumber(-4294967231.0).to_uint16(), 65);
335 }
336
337 #[test]
338 fn test_add() {
339 assert_eq!(JsNumber(1.0) + JsNumber(2.0), JsNumber(3.0));
340
341 assert!((JsNumber(1.0) + JsNumber(f64::NAN)).is_nan());
342 assert!((JsNumber(f64::NAN) + JsNumber(1.0)).is_nan());
343 assert!((JsNumber(f64::NAN) + JsNumber(f64::NAN)).is_nan());
344 assert!((JsNumber(f64::INFINITY) + JsNumber(f64::NEG_INFINITY)).is_nan());
345 assert!((JsNumber(f64::NEG_INFINITY) + JsNumber(f64::INFINITY)).is_nan());
346
347 assert_eq!(
348 JsNumber(f64::INFINITY) + JsNumber(1.0),
349 JsNumber(f64::INFINITY)
350 );
351 assert_eq!(
352 JsNumber(f64::NEG_INFINITY) + JsNumber(1.0),
353 JsNumber(f64::NEG_INFINITY)
354 );
355 assert_eq!(JsNumber(-0.0) + JsNumber(0.0), JsNumber(-0.0));
356 }
357}