avm2: Re-use code in `coerce_to_number`
Use `string_to_f64` and `string_to_int`, as [`MathUtils::convertStringToNumber`](858d034a3b/core/MathUtils.cpp (L453-L466)
)
in avmplus does.
This commit is contained in:
parent
265dcd2e8d
commit
9c1a05aaaf
|
@ -54,82 +54,6 @@ pub fn is_nan<'gc>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a `WStr` to an integer (as an `f64`).
|
|
||||||
///
|
|
||||||
/// This function might fail for some invalid inputs, by returning `f64::NAN`.
|
|
||||||
///
|
|
||||||
/// `radix` is only valid in the range `2..=36`, plus the special `0` value, which means the
|
|
||||||
/// radix is inferred from the string; hexadecimal if it starts with a `0x` prefix (case
|
|
||||||
/// insensitive), or decimal otherwise.
|
|
||||||
/// `strict` tells whether to fail on trailing garbage, or ignore it.
|
|
||||||
fn string_to_int(mut s: &WStr, mut radix: i32, strict: bool) -> f64 {
|
|
||||||
// Allow leading whitespace.
|
|
||||||
skip_spaces(&mut s);
|
|
||||||
|
|
||||||
let is_negative = parse_sign(&mut s);
|
|
||||||
|
|
||||||
if radix == 16 || radix == 0 {
|
|
||||||
if let Some(after_0x) = s
|
|
||||||
.strip_prefix(WStr::from_units(b"0x"))
|
|
||||||
.or_else(|| s.strip_prefix(WStr::from_units(b"0X")))
|
|
||||||
{
|
|
||||||
// Consume hexadecimal prefix.
|
|
||||||
s = after_0x;
|
|
||||||
|
|
||||||
// Explicit hexadecimal.
|
|
||||||
radix = 16;
|
|
||||||
} else if radix == 0 {
|
|
||||||
// Default to decimal.
|
|
||||||
radix = 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail on invalid radix or blank string.
|
|
||||||
if !(2..=36).contains(&radix) || s.is_empty() {
|
|
||||||
return f64::NAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actual number parsing.
|
|
||||||
let mut result = 0.0;
|
|
||||||
let start = s;
|
|
||||||
s = s.trim_start_matches(|c| {
|
|
||||||
match u8::try_from(c)
|
|
||||||
.ok()
|
|
||||||
.and_then(|c| char::from(c).to_digit(radix as u32))
|
|
||||||
{
|
|
||||||
Some(digit) => {
|
|
||||||
result *= f64::from(radix);
|
|
||||||
result += f64::from(digit);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fail if we got no digits.
|
|
||||||
// TODO: Compare by reference instead?
|
|
||||||
if s.len() == start.len() {
|
|
||||||
return f64::NAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
if strict {
|
|
||||||
// Allow trailing whitespace.
|
|
||||||
skip_spaces(&mut s);
|
|
||||||
|
|
||||||
// Fail if we got digits, but we're in strict mode and not at end of string.
|
|
||||||
if !s.is_empty() {
|
|
||||||
return f64::NAN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply sign.
|
|
||||||
if is_negative {
|
|
||||||
result = -result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_int<'gc>(
|
pub fn parse_int<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
_this: Option<Object<'gc>>,
|
_this: Option<Object<'gc>>,
|
||||||
|
@ -145,224 +69,10 @@ pub fn parse_int<'gc>(
|
||||||
None => 0,
|
None => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = string_to_int(&string, radix, false);
|
let result = crate::avm2::value::string_to_int(&string, radix, false);
|
||||||
Ok(result.into())
|
Ok(result.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strips leading whitespace.
|
|
||||||
fn skip_spaces(s: &mut &WStr) {
|
|
||||||
*s = s.trim_start_matches(|c| {
|
|
||||||
matches!(
|
|
||||||
c,
|
|
||||||
0x20 | 0x09 | 0x0d | 0x0a | 0x0c | 0x0b | 0x2000
|
|
||||||
..=0x200b | 0x2028 | 0x2029 | 0x205f | 0x3000
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes an optional sign character.
|
|
||||||
/// Returns whether a minus sign was consumed.
|
|
||||||
fn parse_sign(s: &mut &WStr) -> bool {
|
|
||||||
if let Some(after_sign) = s.strip_prefix(b'-') {
|
|
||||||
*s = after_sign;
|
|
||||||
true
|
|
||||||
} else if let Some(after_sign) = s.strip_prefix(b'+') {
|
|
||||||
*s = after_sign;
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a `WStr` to an `f64`.
|
|
||||||
///
|
|
||||||
/// This function might fail for some invalid inputs, by returning `None`.
|
|
||||||
///
|
|
||||||
/// `strict` typically tells whether to behave like `Number()` or `parseFloat()`:
|
|
||||||
/// * `strict == true` fails on trailing garbage, but interprets blank strings (which are empty or consist only of whitespace) as zero.
|
|
||||||
/// * `strict == false` ignores trailing garbage, but fails on blank strings.
|
|
||||||
fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
|
|
||||||
fn is_ascii_digit(c: u16) -> bool {
|
|
||||||
u8::try_from(c).map_or(false, |c| c.is_ascii_digit())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow leading whitespace.
|
|
||||||
skip_spaces(&mut s);
|
|
||||||
|
|
||||||
// Handle blank strings as described above.
|
|
||||||
if s.is_empty() {
|
|
||||||
return if strict { Some(0.0) } else { None };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse sign.
|
|
||||||
let is_negative = parse_sign(&mut s);
|
|
||||||
let after_sign = s;
|
|
||||||
|
|
||||||
// Count digits before decimal point.
|
|
||||||
s = s.trim_start_matches(is_ascii_digit);
|
|
||||||
let mut total_digits = after_sign.len() - s.len();
|
|
||||||
|
|
||||||
// Count digits after decimal point.
|
|
||||||
if let Some(after_dot) = s.strip_prefix(b'.') {
|
|
||||||
s = after_dot;
|
|
||||||
s = s.trim_start_matches(is_ascii_digit);
|
|
||||||
total_digits += after_dot.len() - s.len();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle exponent.
|
|
||||||
let mut exponent: i32 = 0;
|
|
||||||
if let Some(after_e) = s.strip_prefix(b"eE".as_ref()) {
|
|
||||||
s = after_e;
|
|
||||||
|
|
||||||
// Parse exponent sign.
|
|
||||||
let exponent_is_negative = parse_sign(&mut s);
|
|
||||||
|
|
||||||
// Fail if string ends with "e-" with no exponent value specified.
|
|
||||||
if exponent_is_negative && s.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse exponent itself.
|
|
||||||
s = s.trim_start_matches(|c| {
|
|
||||||
match u8::try_from(c)
|
|
||||||
.ok()
|
|
||||||
.and_then(|c| char::from(c).to_digit(10))
|
|
||||||
{
|
|
||||||
Some(digit) => {
|
|
||||||
exponent = exponent.wrapping_mul(10);
|
|
||||||
exponent = exponent.wrapping_add(digit as i32);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply exponent sign.
|
|
||||||
if exponent_is_negative {
|
|
||||||
exponent = exponent.wrapping_neg();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow trailing whitespace.
|
|
||||||
skip_spaces(&mut s);
|
|
||||||
|
|
||||||
// If we got no digits, check for Infinity/-Infinity. Otherwise fail.
|
|
||||||
if total_digits == 0 {
|
|
||||||
if let Some(after_infinity) = s.strip_prefix(WStr::from_units(b"Infinity")) {
|
|
||||||
s = after_infinity;
|
|
||||||
|
|
||||||
// Allow end of string or a whitespace. Otherwise fail.
|
|
||||||
if !s.is_empty() {
|
|
||||||
skip_spaces(&mut s);
|
|
||||||
// TODO: Compare by reference instead?
|
|
||||||
if s.len() == after_infinity.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = if is_negative {
|
|
||||||
f64::NEG_INFINITY
|
|
||||||
} else {
|
|
||||||
f64::INFINITY
|
|
||||||
};
|
|
||||||
return Some(result);
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if we got digits, but we're in strict mode and not at end of string or at a null character.
|
|
||||||
if strict && !s.is_empty() && !s.starts_with(b'\0') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug compatibility: https://bugzilla.mozilla.org/show_bug.cgi?id=513018
|
|
||||||
let s = if swf_version >= 11 {
|
|
||||||
&after_sign[..after_sign.len() - s.len()]
|
|
||||||
} else {
|
|
||||||
after_sign
|
|
||||||
};
|
|
||||||
|
|
||||||
// Finally, calculate the result.
|
|
||||||
let mut result = if total_digits > 15 {
|
|
||||||
// With more than 15 digits, avmplus uses integer arithmetic to avoid rounding errors.
|
|
||||||
let mut result: i64 = 0;
|
|
||||||
let mut decimal_digits = -1;
|
|
||||||
for c in s {
|
|
||||||
if let Some(digit) = u8::try_from(c)
|
|
||||||
.ok()
|
|
||||||
.and_then(|c| char::from(c).to_digit(10))
|
|
||||||
{
|
|
||||||
if decimal_digits != -1 {
|
|
||||||
decimal_digits += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
result *= 10;
|
|
||||||
result += i64::from(digit);
|
|
||||||
} else if c == b'.' as u16 {
|
|
||||||
decimal_digits = 0;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if decimal_digits > 0 {
|
|
||||||
exponent -= decimal_digits;
|
|
||||||
}
|
|
||||||
|
|
||||||
if exponent > 0 {
|
|
||||||
result *= i64::pow(10, exponent as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
result as f64
|
|
||||||
} else {
|
|
||||||
let mut result = 0.0;
|
|
||||||
let mut decimal_digits = -1;
|
|
||||||
for c in s {
|
|
||||||
if let Some(digit) = u8::try_from(c)
|
|
||||||
.ok()
|
|
||||||
.and_then(|c| char::from(c).to_digit(10))
|
|
||||||
{
|
|
||||||
if decimal_digits != -1 {
|
|
||||||
decimal_digits += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
result *= 10.0;
|
|
||||||
result += digit as f64;
|
|
||||||
} else if c == b'.' as u16 {
|
|
||||||
decimal_digits = 0;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if decimal_digits > 0 {
|
|
||||||
exponent -= decimal_digits;
|
|
||||||
}
|
|
||||||
|
|
||||||
if exponent > 0 {
|
|
||||||
result *= f64::powi(10.0, exponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
};
|
|
||||||
|
|
||||||
if exponent < 0 {
|
|
||||||
if exponent < -307 {
|
|
||||||
let diff = exponent + 307;
|
|
||||||
result /= f64::powi(10.0, -diff);
|
|
||||||
exponent = -307;
|
|
||||||
}
|
|
||||||
result /= f64::powi(10.0, -exponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply sign.
|
|
||||||
if is_negative {
|
|
||||||
result = -result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_float<'gc>(
|
pub fn parse_float<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
_this: Option<Object<'gc>>,
|
_this: Option<Object<'gc>>,
|
||||||
|
@ -371,7 +81,7 @@ pub fn parse_float<'gc>(
|
||||||
if let Some(value) = args.get(0) {
|
if let Some(value) = args.get(0) {
|
||||||
let string = value.coerce_to_string(activation)?;
|
let string = value.coerce_to_string(activation)?;
|
||||||
let swf_version = activation.context.swf.version();
|
let swf_version = activation.context.swf.version();
|
||||||
if let Some(result) = string_to_f64(&string, swf_version, false) {
|
if let Some(result) = crate::avm2::value::string_to_f64(&string, swf_version, false) {
|
||||||
return Ok(result.into());
|
return Ok(result.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,295 @@ impl PartialEq for Value<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strips leading whitespace.
|
||||||
|
fn skip_spaces(s: &mut &WStr) {
|
||||||
|
*s = s.trim_start_matches(|c| {
|
||||||
|
matches!(
|
||||||
|
c,
|
||||||
|
0x20 | 0x09 | 0x0d | 0x0a | 0x0c | 0x0b | 0x2000
|
||||||
|
..=0x200b | 0x2028 | 0x2029 | 0x205f | 0x3000
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes an optional sign character.
|
||||||
|
/// Returns whether a minus sign was consumed.
|
||||||
|
fn parse_sign(s: &mut &WStr) -> bool {
|
||||||
|
if let Some(after_sign) = s.strip_prefix(b'-') {
|
||||||
|
*s = after_sign;
|
||||||
|
true
|
||||||
|
} else if let Some(after_sign) = s.strip_prefix(b'+') {
|
||||||
|
*s = after_sign;
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a `WStr` to an integer (as an `f64`).
|
||||||
|
///
|
||||||
|
/// This function might fail for some invalid inputs, by returning `f64::NAN`.
|
||||||
|
///
|
||||||
|
/// `radix` is only valid in the range `2..=36`, plus the special `0` value, which means the
|
||||||
|
/// radix is inferred from the string; hexadecimal if it starts with a `0x` prefix (case
|
||||||
|
/// insensitive), or decimal otherwise.
|
||||||
|
/// `strict` tells whether to fail on trailing garbage, or ignore it.
|
||||||
|
pub fn string_to_int(mut s: &WStr, mut radix: i32, strict: bool) -> f64 {
|
||||||
|
// Allow leading whitespace.
|
||||||
|
skip_spaces(&mut s);
|
||||||
|
|
||||||
|
let is_negative = parse_sign(&mut s);
|
||||||
|
|
||||||
|
if radix == 16 || radix == 0 {
|
||||||
|
if let Some(after_0x) = s
|
||||||
|
.strip_prefix(WStr::from_units(b"0x"))
|
||||||
|
.or_else(|| s.strip_prefix(WStr::from_units(b"0X")))
|
||||||
|
{
|
||||||
|
// Consume hexadecimal prefix.
|
||||||
|
s = after_0x;
|
||||||
|
|
||||||
|
// Explicit hexadecimal.
|
||||||
|
radix = 16;
|
||||||
|
} else if radix == 0 {
|
||||||
|
// Default to decimal.
|
||||||
|
radix = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail on invalid radix or blank string.
|
||||||
|
if !(2..=36).contains(&radix) || s.is_empty() {
|
||||||
|
return f64::NAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actual number parsing.
|
||||||
|
let mut result = 0.0;
|
||||||
|
let start = s;
|
||||||
|
s = s.trim_start_matches(|c| {
|
||||||
|
match u8::try_from(c)
|
||||||
|
.ok()
|
||||||
|
.and_then(|c| char::from(c).to_digit(radix as u32))
|
||||||
|
{
|
||||||
|
Some(digit) => {
|
||||||
|
result *= f64::from(radix);
|
||||||
|
result += f64::from(digit);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fail if we got no digits.
|
||||||
|
// TODO: Compare by reference instead?
|
||||||
|
if s.len() == start.len() {
|
||||||
|
return f64::NAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if strict {
|
||||||
|
// Allow trailing whitespace.
|
||||||
|
skip_spaces(&mut s);
|
||||||
|
|
||||||
|
// Fail if we got digits, but we're in strict mode and not at end of string.
|
||||||
|
if !s.is_empty() {
|
||||||
|
return f64::NAN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sign.
|
||||||
|
if is_negative {
|
||||||
|
result = -result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should only return integers and +/-Infinity.
|
||||||
|
debug_assert!(result.is_infinite() || result.fract() == 0.0);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a `WStr` to an `f64`.
|
||||||
|
///
|
||||||
|
/// This function might fail for some invalid inputs, by returning `None`.
|
||||||
|
///
|
||||||
|
/// `strict` typically tells whether to behave like `Number()` or `parseFloat()`:
|
||||||
|
/// * `strict == true` fails on trailing garbage, but interprets blank strings (which are empty or consist only of whitespace) as zero.
|
||||||
|
/// * `strict == false` ignores trailing garbage, but fails on blank strings.
|
||||||
|
pub fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
|
||||||
|
fn is_ascii_digit(c: u16) -> bool {
|
||||||
|
u8::try_from(c).map_or(false, |c| c.is_ascii_digit())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_decimal_digit(c: u16) -> Option<u32> {
|
||||||
|
u8::try_from(c)
|
||||||
|
.ok()
|
||||||
|
.and_then(|c| char::from(c).to_digit(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow leading whitespace.
|
||||||
|
skip_spaces(&mut s);
|
||||||
|
|
||||||
|
// Handle blank strings as described above.
|
||||||
|
if s.is_empty() {
|
||||||
|
return if strict { Some(0.0) } else { None };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse sign.
|
||||||
|
let is_negative = parse_sign(&mut s);
|
||||||
|
let after_sign = s;
|
||||||
|
|
||||||
|
// Count digits before decimal point.
|
||||||
|
s = s.trim_start_matches(is_ascii_digit);
|
||||||
|
let mut total_digits = after_sign.len() - s.len();
|
||||||
|
|
||||||
|
// Count digits after decimal point.
|
||||||
|
if let Some(after_dot) = s.strip_prefix(b'.') {
|
||||||
|
s = after_dot;
|
||||||
|
s = s.trim_start_matches(is_ascii_digit);
|
||||||
|
total_digits += after_dot.len() - s.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle exponent.
|
||||||
|
let mut exponent: i32 = 0;
|
||||||
|
if let Some(after_e) = s.strip_prefix(b"eE".as_ref()) {
|
||||||
|
s = after_e;
|
||||||
|
|
||||||
|
// Parse exponent sign.
|
||||||
|
let exponent_is_negative = parse_sign(&mut s);
|
||||||
|
|
||||||
|
// Fail if string ends with "e-" with no exponent value specified.
|
||||||
|
if exponent_is_negative && s.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse exponent itself.
|
||||||
|
s = s.trim_start_matches(|c| match to_decimal_digit(c) {
|
||||||
|
Some(digit) => {
|
||||||
|
exponent = exponent.wrapping_mul(10);
|
||||||
|
exponent = exponent.wrapping_add(digit as i32);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply exponent sign.
|
||||||
|
if exponent_is_negative {
|
||||||
|
exponent = exponent.wrapping_neg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow trailing whitespace.
|
||||||
|
skip_spaces(&mut s);
|
||||||
|
|
||||||
|
// If we got no digits, check for Infinity/-Infinity. Otherwise fail.
|
||||||
|
if total_digits == 0 {
|
||||||
|
if let Some(after_infinity) = s.strip_prefix(WStr::from_units(b"Infinity")) {
|
||||||
|
s = after_infinity;
|
||||||
|
|
||||||
|
// Allow end of string or a whitespace. Otherwise fail.
|
||||||
|
if !s.is_empty() {
|
||||||
|
skip_spaces(&mut s);
|
||||||
|
// TODO: Compare by reference instead?
|
||||||
|
if s.len() == after_infinity.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = if is_negative {
|
||||||
|
f64::NEG_INFINITY
|
||||||
|
} else {
|
||||||
|
f64::INFINITY
|
||||||
|
};
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if we got digits, but we're in strict mode and not at end of string or at a null character.
|
||||||
|
if strict && !s.is_empty() && !s.starts_with(b'\0') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bug compatibility: https://bugzilla.mozilla.org/show_bug.cgi?id=513018
|
||||||
|
let s = if swf_version >= 11 {
|
||||||
|
&after_sign[..after_sign.len() - s.len()]
|
||||||
|
} else {
|
||||||
|
after_sign
|
||||||
|
};
|
||||||
|
|
||||||
|
// Finally, calculate the result.
|
||||||
|
let mut result = if total_digits > 15 {
|
||||||
|
// With more than 15 digits, avmplus uses integer arithmetic to avoid rounding errors.
|
||||||
|
let mut result: i64 = 0;
|
||||||
|
let mut decimal_digits = -1;
|
||||||
|
for c in s {
|
||||||
|
if let Some(digit) = to_decimal_digit(c) {
|
||||||
|
if decimal_digits != -1 {
|
||||||
|
decimal_digits += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result *= 10;
|
||||||
|
result += i64::from(digit);
|
||||||
|
} else if c == b'.' as u16 {
|
||||||
|
decimal_digits = 0;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if decimal_digits > 0 {
|
||||||
|
exponent -= decimal_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
if exponent > 0 {
|
||||||
|
result *= i64::pow(10, exponent as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
result as f64
|
||||||
|
} else {
|
||||||
|
let mut result = 0.0;
|
||||||
|
let mut decimal_digits = -1;
|
||||||
|
for c in s {
|
||||||
|
if let Some(digit) = to_decimal_digit(c) {
|
||||||
|
if decimal_digits != -1 {
|
||||||
|
decimal_digits += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result *= 10.0;
|
||||||
|
result += digit as f64;
|
||||||
|
} else if c == b'.' as u16 {
|
||||||
|
decimal_digits = 0;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if decimal_digits > 0 {
|
||||||
|
exponent -= decimal_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
if exponent > 0 {
|
||||||
|
result *= f64::powi(10.0, exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
if exponent < 0 {
|
||||||
|
if exponent < -307 {
|
||||||
|
let diff = exponent + 307;
|
||||||
|
result /= f64::powi(10.0, -diff);
|
||||||
|
exponent = -307;
|
||||||
|
}
|
||||||
|
result /= f64::powi(10.0, -exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sign.
|
||||||
|
if is_negative {
|
||||||
|
result = -result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We shouldn't return `NaN` after a successful parsing.
|
||||||
|
debug_assert!(!result.is_nan());
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn abc_int(translation_unit: TranslationUnit<'_>, index: Index<i32>) -> Result<i32, Error> {
|
pub fn abc_int(translation_unit: TranslationUnit<'_>, index: Index<i32>) -> Result<i32, Error> {
|
||||||
if index.0 == 0 {
|
if index.0 == 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
|
@ -384,44 +673,8 @@ impl<'gc> Value<'gc> {
|
||||||
Value::Unsigned(u) => *u as f64,
|
Value::Unsigned(u) => *u as f64,
|
||||||
Value::Integer(i) => *i as f64,
|
Value::Integer(i) => *i as f64,
|
||||||
Value::String(s) => {
|
Value::String(s) => {
|
||||||
let strim = s.trim();
|
let swf_version = activation.context.swf.version();
|
||||||
if strim.is_empty() {
|
string_to_f64(s, swf_version, true).unwrap_or_else(|| string_to_int(s, 0, true))
|
||||||
0.0
|
|
||||||
} else if strim.starts_with(WStr::from_units(b"0x"))
|
|
||||||
|| strim.starts_with(WStr::from_units(b"0X"))
|
|
||||||
{
|
|
||||||
let mut n: f64 = 0.0;
|
|
||||||
for c in &strim[2..] {
|
|
||||||
let digit = u8::try_from(c).ok().and_then(|c| (c as char).to_digit(16));
|
|
||||||
if let Some(digit) = digit {
|
|
||||||
n = 16.0 * n + f64::from(digit);
|
|
||||||
} else {
|
|
||||||
return Ok(f64::NAN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n
|
|
||||||
} else {
|
|
||||||
let (sign, digits) = if let Some(stripped) = strim.strip_prefix(b'+') {
|
|
||||||
(1.0, stripped)
|
|
||||||
} else if let Some(stripped) = strim.strip_prefix(b'-') {
|
|
||||||
(-1.0, stripped)
|
|
||||||
} else {
|
|
||||||
(1.0, strim)
|
|
||||||
};
|
|
||||||
|
|
||||||
if digits == b"Infinity" {
|
|
||||||
return Ok(sign * f64::INFINITY);
|
|
||||||
} else if digits.starts_with([b'i', b'I'].as_ref()) {
|
|
||||||
// Avoid Rust f64::parse accepting "inf" and "infinity"
|
|
||||||
return Ok(f64::NAN);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: This is slightly more permissive than ES3 spec, as
|
|
||||||
//Rust documentation claims it will accept "inf" as f64
|
|
||||||
//infinity.
|
|
||||||
sign * digits.parse().unwrap_or(f64::NAN)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Value::Object(_) => self
|
Value::Object(_) => self
|
||||||
.coerce_to_primitive(Some(Hint::Number), activation)?
|
.coerce_to_primitive(Some(Hint::Number), activation)?
|
||||||
|
|
Loading…
Reference in New Issue