alibi_core/utils/
javascript.rs1#[must_use]
4pub fn trim(value: &str) -> &str {
5 value.trim_matches(is_whitespace)
6}
7
8#[must_use]
9pub const fn is_whitespace(character: char) -> bool {
10 matches!(
11 character,
12 '\u{0009}'..='\u{000D}'
13 | '\u{0020}'
14 | '\u{00A0}'
15 | '\u{1680}'
16 | '\u{2000}'..='\u{200A}'
17 | '\u{2028}'
18 | '\u{2029}'
19 | '\u{202F}'
20 | '\u{205F}'
21 | '\u{3000}'
22 | '\u{FEFF}'
23 )
24}
25
26#[must_use]
27pub fn string_to_number(value: &str) -> Option<f64> {
28 let value = trim(value);
29 if value.is_empty() {
30 return Some(0.0);
31 }
32 for (prefixes, radix, bits) in [
33 (["0x", "0X"], 16, 4),
34 (["0o", "0O"], 8, 3),
35 (["0b", "0B"], 2, 1),
36 ] {
37 if let Some(digits) = prefixes
38 .iter()
39 .find_map(|prefix| value.strip_prefix(prefix))
40 {
41 return radix_number(digits, radix, bits);
42 }
43 }
44 match value {
45 "Infinity" | "+Infinity" => Some(f64::INFINITY),
46 "-Infinity" => Some(f64::NEG_INFINITY),
47 _ if value.bytes().all(|byte| {
48 byte.is_ascii_digit() || matches!(byte, b'+' | b'-' | b'.' | b'e' | b'E')
49 }) =>
50 {
51 value.parse().ok()
52 }
53 _ => None,
54 }
55}
56
57#[expect(
61 clippy::as_conversions,
62 clippy::cast_possible_truncation,
63 clippy::cast_possible_wrap,
64 clippy::cast_precision_loss,
65 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
66)]
67fn radix_number(digits: &str, radix: u32, bits_per_digit: usize) -> Option<f64> {
68 if digits.is_empty() {
69 return None;
70 }
71 let digits = digits
72 .chars()
73 .map(|character| character.to_digit(radix))
74 .collect::<Option<Vec<_>>>()?;
75 let Some(first_nonzero) = digits.iter().position(|digit| *digit != 0) else {
76 return Some(0.0);
77 };
78 let significant = digits.get(first_nonzero..)?;
79 let first = *significant.first()?;
80 let first_bits = first.bit_width() as usize;
81 let bit_length = first_bits + (significant.len() - 1) * bits_per_digit;
82 if bit_length > 1024 {
83 return Some(f64::INFINITY);
84 }
85 let mut mantissa = 0u64;
86 let mut position = 0;
87 let mut guard = false;
88 let mut sticky = false;
89 for (index, digit) in significant.iter().enumerate() {
90 let width = if index == 0 {
91 first_bits
92 } else {
93 bits_per_digit
94 };
95 for bit in (0..width).rev() {
96 let set = (*digit >> bit) & 1 != 0;
97 match position.cmp(&53) {
98 std::cmp::Ordering::Less => mantissa = (mantissa << 1) | u64::from(set),
99 std::cmp::Ordering::Equal => guard = set,
100 std::cmp::Ordering::Greater => sticky |= set,
101 }
102 position += 1;
103 }
104 }
105 if bit_length <= 53 {
106 return Some(mantissa as f64);
107 }
108 if guard && (sticky || mantissa & 1 != 0) {
109 mantissa += 1;
110 }
111 Some(mantissa as f64 * 2.0f64.powi((bit_length - 53) as i32))
112}