Skip to content

Commit 9c1a05a

Browse files
committed
avm2: Re-use code in coerce_to_number
Use `string_to_f64` and `string_to_int`, as [`MathUtils::convertStringToNumber`](https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/MathUtils.cpp#L453-L466) in avmplus does.
1 parent 265dcd2 commit 9c1a05a

File tree

2 files changed

+293
-330
lines changed

2 files changed

+293
-330
lines changed

core/src/avm2/globals/toplevel.rs

Lines changed: 2 additions & 292 deletions
Original file line numberDiff line numberDiff line change
@@ -54,82 +54,6 @@ pub fn is_nan<'gc>(
5454
}
5555
}
5656

57-
/// Converts a `WStr` to an integer (as an `f64`).
58-
///
59-
/// This function might fail for some invalid inputs, by returning `f64::NAN`.
60-
///
61-
/// `radix` is only valid in the range `2..=36`, plus the special `0` value, which means the
62-
/// radix is inferred from the string; hexadecimal if it starts with a `0x` prefix (case
63-
/// insensitive), or decimal otherwise.
64-
/// `strict` tells whether to fail on trailing garbage, or ignore it.
65-
fn string_to_int(mut s: &WStr, mut radix: i32, strict: bool) -> f64 {
66-
// Allow leading whitespace.
67-
skip_spaces(&mut s);
68-
69-
let is_negative = parse_sign(&mut s);
70-
71-
if radix == 16 || radix == 0 {
72-
if let Some(after_0x) = s
73-
.strip_prefix(WStr::from_units(b"0x"))
74-
.or_else(|| s.strip_prefix(WStr::from_units(b"0X")))
75-
{
76-
// Consume hexadecimal prefix.
77-
s = after_0x;
78-
79-
// Explicit hexadecimal.
80-
radix = 16;
81-
} else if radix == 0 {
82-
// Default to decimal.
83-
radix = 10;
84-
}
85-
}
86-
87-
// Fail on invalid radix or blank string.
88-
if !(2..=36).contains(&radix) || s.is_empty() {
89-
return f64::NAN;
90-
}
91-
92-
// Actual number parsing.
93-
let mut result = 0.0;
94-
let start = s;
95-
s = s.trim_start_matches(|c| {
96-
match u8::try_from(c)
97-
.ok()
98-
.and_then(|c| char::from(c).to_digit(radix as u32))
99-
{
100-
Some(digit) => {
101-
result *= f64::from(radix);
102-
result += f64::from(digit);
103-
true
104-
}
105-
None => false,
106-
}
107-
});
108-
109-
// Fail if we got no digits.
110-
// TODO: Compare by reference instead?
111-
if s.len() == start.len() {
112-
return f64::NAN;
113-
}
114-
115-
if strict {
116-
// Allow trailing whitespace.
117-
skip_spaces(&mut s);
118-
119-
// Fail if we got digits, but we're in strict mode and not at end of string.
120-
if !s.is_empty() {
121-
return f64::NAN;
122-
}
123-
}
124-
125-
// Apply sign.
126-
if is_negative {
127-
result = -result;
128-
}
129-
130-
result
131-
}
132-
13357
pub fn parse_int<'gc>(
13458
activation: &mut Activation<'_, 'gc, '_>,
13559
_this: Option<Object<'gc>>,
@@ -145,224 +69,10 @@ pub fn parse_int<'gc>(
14569
None => 0,
14670
};
14771

148-
let result = string_to_int(&string, radix, false);
72+
let result = crate::avm2::value::string_to_int(&string, radix, false);
14973
Ok(result.into())
15074
}
15175

152-
/// Strips leading whitespace.
153-
fn skip_spaces(s: &mut &WStr) {
154-
*s = s.trim_start_matches(|c| {
155-
matches!(
156-
c,
157-
0x20 | 0x09 | 0x0d | 0x0a | 0x0c | 0x0b | 0x2000
158-
..=0x200b | 0x2028 | 0x2029 | 0x205f | 0x3000
159-
)
160-
});
161-
}
162-
163-
/// Consumes an optional sign character.
164-
/// Returns whether a minus sign was consumed.
165-
fn parse_sign(s: &mut &WStr) -> bool {
166-
if let Some(after_sign) = s.strip_prefix(b'-') {
167-
*s = after_sign;
168-
true
169-
} else if let Some(after_sign) = s.strip_prefix(b'+') {
170-
*s = after_sign;
171-
false
172-
} else {
173-
false
174-
}
175-
}
176-
177-
/// Converts a `WStr` to an `f64`.
178-
///
179-
/// This function might fail for some invalid inputs, by returning `None`.
180-
///
181-
/// `strict` typically tells whether to behave like `Number()` or `parseFloat()`:
182-
/// * `strict == true` fails on trailing garbage, but interprets blank strings (which are empty or consist only of whitespace) as zero.
183-
/// * `strict == false` ignores trailing garbage, but fails on blank strings.
184-
fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
185-
fn is_ascii_digit(c: u16) -> bool {
186-
u8::try_from(c).map_or(false, |c| c.is_ascii_digit())
187-
}
188-
189-
// Allow leading whitespace.
190-
skip_spaces(&mut s);
191-
192-
// Handle blank strings as described above.
193-
if s.is_empty() {
194-
return if strict { Some(0.0) } else { None };
195-
}
196-
197-
// Parse sign.
198-
let is_negative = parse_sign(&mut s);
199-
let after_sign = s;
200-
201-
// Count digits before decimal point.
202-
s = s.trim_start_matches(is_ascii_digit);
203-
let mut total_digits = after_sign.len() - s.len();
204-
205-
// Count digits after decimal point.
206-
if let Some(after_dot) = s.strip_prefix(b'.') {
207-
s = after_dot;
208-
s = s.trim_start_matches(is_ascii_digit);
209-
total_digits += after_dot.len() - s.len();
210-
}
211-
212-
// Handle exponent.
213-
let mut exponent: i32 = 0;
214-
if let Some(after_e) = s.strip_prefix(b"eE".as_ref()) {
215-
s = after_e;
216-
217-
// Parse exponent sign.
218-
let exponent_is_negative = parse_sign(&mut s);
219-
220-
// Fail if string ends with "e-" with no exponent value specified.
221-
if exponent_is_negative && s.is_empty() {
222-
return None;
223-
}
224-
225-
// Parse exponent itself.
226-
s = s.trim_start_matches(|c| {
227-
match u8::try_from(c)
228-
.ok()
229-
.and_then(|c| char::from(c).to_digit(10))
230-
{
231-
Some(digit) => {
232-
exponent = exponent.wrapping_mul(10);
233-
exponent = exponent.wrapping_add(digit as i32);
234-
true
235-
}
236-
None => false,
237-
}
238-
});
239-
240-
// Apply exponent sign.
241-
if exponent_is_negative {
242-
exponent = exponent.wrapping_neg();
243-
}
244-
}
245-
246-
// Allow trailing whitespace.
247-
skip_spaces(&mut s);
248-
249-
// If we got no digits, check for Infinity/-Infinity. Otherwise fail.
250-
if total_digits == 0 {
251-
if let Some(after_infinity) = s.strip_prefix(WStr::from_units(b"Infinity")) {
252-
s = after_infinity;
253-
254-
// Allow end of string or a whitespace. Otherwise fail.
255-
if !s.is_empty() {
256-
skip_spaces(&mut s);
257-
// TODO: Compare by reference instead?
258-
if s.len() == after_infinity.len() {
259-
return None;
260-
}
261-
}
262-
263-
let result = if is_negative {
264-
f64::NEG_INFINITY
265-
} else {
266-
f64::INFINITY
267-
};
268-
return Some(result);
269-
}
270-
return None;
271-
}
272-
273-
// Fail if we got digits, but we're in strict mode and not at end of string or at a null character.
274-
if strict && !s.is_empty() && !s.starts_with(b'\0') {
275-
return None;
276-
}
277-
278-
// Bug compatibility: https://bugzilla.mozilla.org/show_bug.cgi?id=513018
279-
let s = if swf_version >= 11 {
280-
&after_sign[..after_sign.len() - s.len()]
281-
} else {
282-
after_sign
283-
};
284-
285-
// Finally, calculate the result.
286-
let mut result = if total_digits > 15 {
287-
// With more than 15 digits, avmplus uses integer arithmetic to avoid rounding errors.
288-
let mut result: i64 = 0;
289-
let mut decimal_digits = -1;
290-
for c in s {
291-
if let Some(digit) = u8::try_from(c)
292-
.ok()
293-
.and_then(|c| char::from(c).to_digit(10))
294-
{
295-
if decimal_digits != -1 {
296-
decimal_digits += 1;
297-
}
298-
299-
result *= 10;
300-
result += i64::from(digit);
301-
} else if c == b'.' as u16 {
302-
decimal_digits = 0;
303-
} else {
304-
break;
305-
}
306-
}
307-
308-
if decimal_digits > 0 {
309-
exponent -= decimal_digits;
310-
}
311-
312-
if exponent > 0 {
313-
result *= i64::pow(10, exponent as u32);
314-
}
315-
316-
result as f64
317-
} else {
318-
let mut result = 0.0;
319-
let mut decimal_digits = -1;
320-
for c in s {
321-
if let Some(digit) = u8::try_from(c)
322-
.ok()
323-
.and_then(|c| char::from(c).to_digit(10))
324-
{
325-
if decimal_digits != -1 {
326-
decimal_digits += 1;
327-
}
328-
329-
result *= 10.0;
330-
result += digit as f64;
331-
} else if c == b'.' as u16 {
332-
decimal_digits = 0;
333-
} else {
334-
break;
335-
}
336-
}
337-
338-
if decimal_digits > 0 {
339-
exponent -= decimal_digits;
340-
}
341-
342-
if exponent > 0 {
343-
result *= f64::powi(10.0, exponent);
344-
}
345-
346-
result
347-
};
348-
349-
if exponent < 0 {
350-
if exponent < -307 {
351-
let diff = exponent + 307;
352-
result /= f64::powi(10.0, -diff);
353-
exponent = -307;
354-
}
355-
result /= f64::powi(10.0, -exponent);
356-
}
357-
358-
// Apply sign.
359-
if is_negative {
360-
result = -result;
361-
}
362-
363-
Some(result)
364-
}
365-
36676
pub fn parse_float<'gc>(
36777
activation: &mut Activation<'_, 'gc, '_>,
36878
_this: Option<Object<'gc>>,
@@ -371,7 +81,7 @@ pub fn parse_float<'gc>(
37181
if let Some(value) = args.get(0) {
37282
let string = value.coerce_to_string(activation)?;
37383
let swf_version = activation.context.swf.version();
374-
if let Some(result) = string_to_f64(&string, swf_version, false) {
84+
if let Some(result) = crate::avm2::value::string_to_f64(&string, swf_version, false) {
37585
return Ok(result.into());
37686
}
37787
}

0 commit comments

Comments
 (0)