|
1 | 1 | use crate::{ |
2 | 2 | Context, JsArgs, JsResult, JsStr, JsString, JsValue, |
3 | | - builtins::{BuiltInBuilder, BuiltInObject, IntrinsicObject, string::is_trimmable_whitespace}, |
| 3 | + builtins::{BuiltInBuilder, BuiltInObject, IntrinsicObject}, |
4 | 4 | context::intrinsics::Intrinsics, |
5 | 5 | object::JsObject, |
6 | 6 | realm::Realm, |
7 | 7 | string::StaticJsStrings, |
8 | 8 | }; |
9 | 9 |
|
10 | 10 | use boa_macros::js_str; |
11 | | -use cow_utils::CowUtils; |
| 11 | +use boa_string::JsStrVariant; |
12 | 12 |
|
13 | 13 | /// Builtin javascript 'isFinite(number)' function. |
14 | 14 | /// |
@@ -154,17 +154,23 @@ fn from_js_str_radix(src: JsStr<'_>, radix: u8) -> Option<f64> { |
154 | 154 | /// [spec]: https://tc39.es/ecma262/#sec-parseint-string-radix |
155 | 155 | /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt |
156 | 156 | pub(crate) fn parse_int(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
157 | | - let (Some(val), radix) = (args.first(), args.get_or_undefined(1)) else { |
| 157 | + let (Some(string), radix) = (args.first(), args.get_or_undefined(1)) else { |
158 | 158 | // Not enough arguments to parseInt. |
159 | 159 | return Ok(JsValue::nan()); |
160 | 160 | }; |
161 | 161 |
|
| 162 | + // OPTIMIZATION: We can skip the round-trip when the value is already a number. |
| 163 | + if let Some(int) = string.as_i32() |
| 164 | + && radix.is_null_or_undefined() |
| 165 | + { |
| 166 | + return Ok(JsValue::new(int)); |
| 167 | + } |
| 168 | + |
162 | 169 | // 1. Let inputString be ? ToString(string). |
163 | | - let input_string = val.to_string(context)?; |
| 170 | + let input_string = string.to_string(context)?; |
164 | 171 |
|
165 | 172 | // 2. Let S be ! TrimString(inputString, start). |
166 | 173 | let mut s = input_string.trim_start(); |
167 | | - // let mut |
168 | 174 |
|
169 | 175 | // 3. Let sign be 1. |
170 | 176 | // 4. If S is not empty and the first code unit of S is the code unit 0x002D (HYPHEN-MINUS), |
@@ -297,40 +303,71 @@ pub(crate) fn parse_float( |
297 | 303 | args: &[JsValue], |
298 | 304 | context: &mut Context, |
299 | 305 | ) -> JsResult<JsValue> { |
300 | | - if let Some(val) = args.first() { |
301 | | - // TODO: parse float with optimal utf16 algorithm |
302 | | - let input_string = val.to_string(context)?.to_std_string_escaped(); |
303 | | - let s = input_string.trim_start_matches(is_trimmable_whitespace); |
304 | | - let s_prefix = s.chars().take(4).collect::<String>(); |
305 | | - let s_prefix_lower = s_prefix.cow_to_ascii_lowercase(); |
306 | | - // TODO: write our own lexer to match syntax StrDecimalLiteral |
307 | | - if s.starts_with("Infinity") || s.starts_with("+Infinity") { |
308 | | - Ok(JsValue::new(f64::INFINITY)) |
309 | | - } else if s.starts_with("-Infinity") { |
310 | | - Ok(JsValue::new(f64::NEG_INFINITY)) |
311 | | - } else if s_prefix_lower.starts_with("inf") |
312 | | - || s_prefix_lower.starts_with("+inf") |
313 | | - || s_prefix_lower.starts_with("-inf") |
314 | | - { |
315 | | - // Prevent fast_float from parsing "inf", "+inf" as Infinity and "-inf" as -Infinity |
316 | | - Ok(JsValue::nan()) |
317 | | - } else { |
318 | | - Ok(fast_float2::parse_partial::<f64, _>(s).map_or_else( |
319 | | - |_| JsValue::nan(), |
320 | | - |(f, len)| { |
321 | | - if len > 0 { |
322 | | - JsValue::new(f) |
323 | | - } else { |
324 | | - JsValue::nan() |
325 | | - } |
326 | | - }, |
327 | | - )) |
| 306 | + const PLUS_CHAR: u16 = b'+' as u16; |
| 307 | + const MINUS_CHAR: u16 = b'-' as u16; |
| 308 | + const LOWER_CASE_I_CHAR: u16 = b'i' as u16; |
| 309 | + const UPPER_CASE_I_CHAR: u16 = b'I' as u16; |
| 310 | + |
| 311 | + let Some(string) = args.first() else { |
| 312 | + return Ok(JsValue::nan()); |
| 313 | + }; |
| 314 | + |
| 315 | + // OPTIMIZATION: We can skip the round-trip when the value is already a number. |
| 316 | + if string.is_number() { |
| 317 | + // Special case for negative zero - it should become positive zero |
| 318 | + if string.is_negative_zero() { |
| 319 | + return Ok(JsValue::new(0)); |
328 | 320 | } |
329 | | - } else { |
330 | | - // Not enough arguments to parseFloat. |
331 | | - Ok(JsValue::nan()) |
| 321 | + |
| 322 | + return Ok(string.clone()); |
| 323 | + } |
| 324 | + |
| 325 | + // 1. Let inputString be ? ToString(string). |
| 326 | + let input_string = string.to_string(context)?; |
| 327 | + |
| 328 | + // 2. Let trimmedString be ! TrimString(inputString, start). |
| 329 | + let trimmed_string = input_string.trim_start(); |
| 330 | + |
| 331 | + // 3. Let trimmed be StringToCodePoints(trimmedString). |
| 332 | + // 4. Let trimmedPrefix be the longest prefix of trimmed that satisfies the syntax of a StrDecimalLiteral, which might be trimmed itself. If there is no such prefix, return NaN. |
| 333 | + // 5. Let parsedNumber be ParseText(trimmedPrefix, StrDecimalLiteral). |
| 334 | + // 6. Assert: parsedNumber is a Parse Node. |
| 335 | + // 7. Return the StringNumericValue of parsedNumber. |
| 336 | + let (positive, prefix) = match trimmed_string.get(0) { |
| 337 | + Some(PLUS_CHAR) => (true, trimmed_string.get(1..).unwrap_or(JsStr::latin1(&[]))), |
| 338 | + Some(MINUS_CHAR) => (false, trimmed_string.get(1..).unwrap_or(JsStr::latin1(&[]))), |
| 339 | + _ => (true, trimmed_string), |
| 340 | + }; |
| 341 | + |
| 342 | + if prefix.starts_with(js_str!("Infinity")) { |
| 343 | + if positive { |
| 344 | + return Ok(JsValue::positive_infinity()); |
| 345 | + } |
| 346 | + return Ok(JsValue::negative_infinity()); |
| 347 | + } else if let Some(LOWER_CASE_I_CHAR | UPPER_CASE_I_CHAR) = prefix.get(0) { |
| 348 | + return Ok(JsValue::nan()); |
332 | 349 | } |
| 350 | + |
| 351 | + let value = match trimmed_string.variant() { |
| 352 | + JsStrVariant::Latin1(s) => fast_float2::parse_partial::<f64, _>(s), |
| 353 | + JsStrVariant::Utf16(s) => { |
| 354 | + let s = String::from_utf16_lossy(s); |
| 355 | + fast_float2::parse_partial::<f64, _>(s.as_bytes()) |
| 356 | + } |
| 357 | + }; |
| 358 | + |
| 359 | + Ok(value.map_or_else( |
| 360 | + |_| JsValue::nan(), |
| 361 | + |(f, len)| { |
| 362 | + if len > 0 { |
| 363 | + JsValue::new(f) |
| 364 | + } else { |
| 365 | + JsValue::nan() |
| 366 | + } |
| 367 | + }, |
| 368 | + )) |
333 | 369 | } |
| 370 | + |
334 | 371 | pub(crate) struct ParseFloat; |
335 | 372 |
|
336 | 373 | impl IntrinsicObject for ParseFloat { |
|
0 commit comments