avm2: Implement parseInt, parseFloat

This commit is contained in:
Adrian Wielgosik 2021-12-13 15:46:57 +01:00 committed by Adrian Wielgosik
parent 49feb23649
commit 11534a4b34
10 changed files with 597 additions and 17 deletions

View File

@ -249,18 +249,7 @@ pub fn get_nan<'gc>(
}
}
pub fn parse_float<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let s = if let Some(val) = args.get(0) {
val.coerce_to_string(activation)?
} else {
return Ok(f64::NAN.into());
};
let s = s.trim_start();
pub fn parse_float_impl(s: &WStr, allow_multiple_dots: bool) -> f64 {
let mut out_str = String::with_capacity(s.len());
// TODO: Implementing this in a very janky way for now,
@ -288,13 +277,17 @@ pub fn parse_float<'gc>(
out_str.push(c.into());
}
b'.' if allow_exp => {
// Flash allows multiple . except after e
allow_sign = false;
if allow_dot {
allow_dot = false;
out_str.push(c.into());
} else {
// AVM1 allows multiple . except after e
if allow_multiple_dots {
allow_exp = false;
} else {
break;
}
}
}
b'e' | b'E' if allow_exp => {
@ -309,8 +302,23 @@ pub fn parse_float<'gc>(
};
}
let n = out_str.parse::<f64>().unwrap_or(f64::NAN);
Ok(n.into())
out_str.parse::<f64>().unwrap_or(f64::NAN)
}
pub fn parse_float<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let s = if let Some(val) = args.get(0) {
val.coerce_to_string(activation)?
} else {
return Ok(f64::NAN.into());
};
let s = s.trim_start();
Ok(parse_float_impl(s, true).into())
}
pub fn set_interval<'gc>(

View File

@ -511,6 +511,8 @@ pub fn load_player_globals<'gc>(
function(activation, "", "trace", toplevel::trace, script)?;
function(activation, "", "isFinite", toplevel::is_finite, script)?;
function(activation, "", "isNaN", toplevel::is_nan, script)?;
function(activation, "", "parseInt", toplevel::parse_int, script)?;
function(activation, "", "parseFloat", toplevel::parse_float, script)?;
constant(mc, "", "undefined", Value::Undefined, script)?;
constant(mc, "", "null", Value::Null, script)?;
constant(mc, "", "NaN", f64::NAN.into(), script)?;

View File

@ -53,3 +53,118 @@ pub fn is_nan<'gc>(
Ok(true.into())
}
}
pub fn parse_int<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
let string = match args.get(0) {
None => return Ok(f64::NAN.into()),
Some(Value::Undefined) => Value::Null,
Some(v) => *v,
}
.coerce_to_string(activation)?;
let radix = if let Some(val) = args.get(1) {
Some(val.coerce_to_u32(activation)?)
} else {
None
};
let radix = match radix {
Some(r @ 2..=36) => Some(r as u32),
Some(0) => None,
Some(_) => return Ok(f64::NAN.into()),
None => None,
};
let string = string.as_wstr();
// Strip spaces.
let string = string.trim_start_matches(b"\t\n\r ".as_ref());
if string.is_empty() {
return Ok(f64::NAN.into());
}
let (sign, string) = match u8::try_from(string.get(0).unwrap()) {
Ok(b'+') => (1.0, &string[1..]),
Ok(b'-') => (-1.0, &string[1..]),
_ => (1.0, string),
};
fn starts_with_0x(string: &WStr) -> bool {
if string.get(0) == Some(b'0' as u16) {
let x_char = string.get(1);
x_char == Some(b'x' as u16) || x_char == Some(b'X' as u16)
} else {
false
}
}
let (radix, string) = match radix {
None => {
if starts_with_0x(string) {
(16, &string[2..])
} else {
(10, string)
}
}
Some(16) => {
if starts_with_0x(string) {
(16, &string[2..])
} else {
(16, string)
}
}
Some(radix) => (radix, string),
};
let mut empty = true;
let mut result = 0.0f64;
for chr in string {
let digit = u8::try_from(chr)
.ok()
.and_then(|c| (c as char).to_digit(radix));
if let Some(digit) = digit {
result = result * radix as f64 + digit as f64;
empty = false;
} else {
break;
}
}
if empty {
Ok(f64::NAN.into())
} else {
Ok(result.copysign(sign).into())
}
}
pub fn parse_float<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
let s = if let Some(val) = args.get(0) {
val.coerce_to_string(activation)?
} else {
return Ok(f64::NAN.into());
};
let s = s.trim_start();
if s.starts_with(WStr::from_units(b"Infinity")) || s.starts_with(WStr::from_units(b"+Infinity"))
{
return Ok(f64::INFINITY.into());
} else if s.starts_with(WStr::from_units(b"-Infinity")) {
return Ok((-f64::INFINITY).into());
}
// TODO: this reuses logic from AVM1,
// which is generally much more lenient.
// There are some cases we should accept (like "- Infinity", but not "- 1")
// And some we should not (like "InfinityXYZ")
use crate::avm1::globals::parse_float_impl;
Ok(parse_float_impl(s, false).into())
}

View File

@ -373,6 +373,7 @@ swf_tests! {
(as3_op_escxattr, "avm2/op_escxattr", 1),
(as3_op_escxelem, "avm2/op_escxelem", 1),
(as3_op_lookupswitch, "avm2/op_lookupswitch", 1),
(as3_parse_int, "avm2/parse_int", 1),
(as3_place_object_replace_2, "avm2/place_object_replace_2", 3),
(as3_place_object_replace, "avm2/place_object_replace", 2),
(as3_point, "avm2/point", 1),
@ -771,6 +772,7 @@ swf_tests_approx! {
(as3_number_toexponential, "avm2/number_toexponential", 1, max_relative = 0.001),
(as3_number_tofixed, "avm2/number_tofixed", 1, max_relative = 0.001),
(as3_number_toprecision, "avm2/number_toprecision", 1, max_relative = 0.001),
(as3_parse_float, "avm2/parse_float", 1, max_relative = 5.0 * f64::EPSILON),
(edittext_align, "avm1/edittext_align", 1, epsilon = 3.0),
(edittext_autosize, "avm1/edittext_autosize", 1, epsilon = 4.0), // TODO Flash has _width higher by 4.0, probably padding logic mistake
(edittext_bullet, "avm1/edittext_bullet", 1, epsilon = 3.0),

View File

@ -0,0 +1,68 @@
// compiled with mxmlc
package {
import flash.display.MovieClip;
public class Test extends MovieClip {
public function Test() {
}
}
}
trace("// parseFloat(\"12345\")");
trace(parseFloat("12345"));
trace("// parseFloat(\"012345.67890\")");
trace(parseFloat("012345.67890"));
trace("// parseFloat(\" 99999.99999 \")");
trace(parseFloat(" \t\r\n99999.99999\t\r\n "));
trace("// parseFloat(\"-22222222222222222\")");
trace(parseFloat("-22222222222222222"));
trace("// parseFloat(\".0000000000000000000000005\")");
trace(parseFloat(".0000000000000000000000005"));
trace("// parseFloat(\"0000.12345GIBBERISH\")");
trace(parseFloat("0000.12345GIBBERISH"));
trace("// parseFloat(\"9e99999\")");
trace(parseFloat("9e99999"));
trace("// parseFloat(\"+100e-100\")");
trace(parseFloat("+100e-100"));
trace("// parseFloat(\"-123.234E+66\")");
trace(parseFloat("-123.234E+66"));
trace("// parseFloat(\".2E20E1\")");
trace(parseFloat(".2E20E1"));
trace("// parseFloat(\"1.2345.678\")");
trace(parseFloat("1.2345.678"));
trace("// parseFloat(\"1.2345.6e50\")");
trace(parseFloat("1.2345.6e50"));
trace("// parseFloat(\"-034.1+e20\")");
trace(parseFloat("-034.1+e20"));
trace("// parseFloat(\"e10\")");
trace(parseFloat("e10"));
trace("// parseFloat(\"BADBAD\")");
trace(parseFloat("BADBAD"));
trace("// parseFloat(\"-\")");
trace(parseFloat("-"));
trace("// parseFloat(\"0xff\")");
trace(parseFloat("0xff"));
trace("// parseFloat(\"Infinity\")");
trace(parseFloat("Infinity"));
trace("// parseFloat(true)");
var b = true;
trace(parseFloat(b));
trace("// parseFloat(1.2)");
var f = 1.2;
trace(parseFloat(f));
trace("// parseFloat(Infinity)");
f = Infinity
trace(parseFloat(f));
trace("// parseFloat({toString})");
var o = {toString:function()
{
return "5";
}};
trace(parseFloat(o));
trace("// parseFloat(new ClassWithToString())");
class C {
public function toString(): String { return "6"; }
}
var c = new C();
trace(parseFloat(c));

View File

@ -0,0 +1,46 @@
// parseFloat("12345")
12345
// parseFloat("012345.67890")
12345.6789
// parseFloat(" 99999.99999 ")
99999.99999
// parseFloat("-22222222222222222")
-22222222222222224
// parseFloat(".0000000000000000000000005")
5e-25
// parseFloat("0000.12345GIBBERISH")
0.12345
// parseFloat("9e99999")
Infinity
// parseFloat("+100e-100")
9.999999999999998e-99
// parseFloat("-123.234E+66")
-1.23234e+68
// parseFloat(".2E20E1")
20000000000000000000
// parseFloat("1.2345.678")
1.2345
// parseFloat("1.2345.6e50")
1.2345
// parseFloat("-034.1+e20")
-34.1
// parseFloat("e10")
NaN
// parseFloat("BADBAD")
NaN
// parseFloat("-")
NaN
// parseFloat("0xff")
0
// parseFloat("Infinity")
Infinity
// parseFloat(true)
NaN
// parseFloat(1.2)
1.2
// parseFloat(Infinity)
Infinity
// parseFloat({toString})
5
// parseFloat(new ClassWithToString())
6

Binary file not shown.

View File

@ -0,0 +1,209 @@
// compiled with mxmlc
package {
import flash.display.MovieClip;
public class Test extends MovieClip {
public function Test() {
}
}
}
var undefined_;
trace("// trace(parseInt()); ")
trace(parseInt());
trace("// trace(parseInt(undefined_)); ")
trace(parseInt(undefined_));
trace("// trace(parseInt(undefined_,32)); ")
trace(parseInt(undefined_,32));
trace("// trace(parseInt(\"\")); ")
trace(parseInt(""));
trace("// trace(parseInt(\"123\")); ")
trace(parseInt("123"));
trace("// trace(parseInt(\"100\",10)); ")
trace(parseInt("100",10));
trace("// trace(parseInt(\"100\",0)); ")
trace(parseInt("100",0));
trace("// trace(parseInt(\"100\",1)); ")
trace(parseInt("100",1));
trace("// trace(parseInt(\"100\",2)); ")
trace(parseInt("100",2));
trace("// trace(parseInt(\"100\",36)); ")
trace(parseInt("100",36));
trace("// trace(parseInt(\"100\",37)); ")
trace(parseInt("100",37));
trace("// trace(parseInt(\"100\",-1)); ")
trace(parseInt("100",-1));
trace("// trace(parseInt(\"100\",{})); ")
var radix = {};
trace(parseInt("100",radix));
trace("// trace(parseInt(\"100\",true)); ")
radix = true;
trace(parseInt("100",radix));
trace("// trace(parseInt(\"100\",false)); ")
radix = false;
trace(parseInt("100",radix));
trace("// trace(parseInt(\"100\",NaN)); ")
trace(parseInt("100",NaN));
trace("// trace(parseInt(\"100\",undefined_)); ")
trace(parseInt("100",undefined_));
trace("// trace(parseInt(\"0x123\")); ")
trace(parseInt("0x123"));
trace("// trace(parseInt(\"0xabc\")); ")
trace(parseInt("0xabc"));
trace("// trace(parseInt(\"010\",2)); ")
trace(parseInt("010",2));
trace("// trace(parseInt(\"-0100\")); ")
trace(parseInt("-0100"));
trace("// trace(parseInt(\"-0100z\")); ")
trace(parseInt("-0100z"));
trace("// trace(parseInt(\"0x+0X100\")); ")
trace(parseInt("0x+0X100"));
trace("// trace(parseInt(123)); ")
var n = 123;
trace(parseInt(n));
trace("// trace(parseInt(123,32)); ")
trace(parseInt(n,32));
trace("// trace(parseInt(\"++1\")); ")
trace(parseInt("++1"));
trace("// trace(parseInt(\"0x100\",36)); ")
trace(parseInt("0x100",36));
trace("// trace(parseInt(\" 0x100\",36)); ")
trace(parseInt(" 0x100",36));
trace("// trace(parseInt(\"0y100\",36)); ")
trace(parseInt("0y100",36));
trace("// trace(parseInt(\" 0y100\",36)); ")
trace(parseInt(" 0y100",36));
trace("// trace(parseInt(\"-0x100\",36)); ")
trace(parseInt("-0x100",36));
trace("// trace(parseInt(\" -0x100\",36)); ")
trace(parseInt(" -0x100",36));
trace("// trace(parseInt(\"-0y100\",36)); ")
trace(parseInt("-0y100",36));
trace("// trace(parseInt(\" -0y100\",36)); ")
trace(parseInt(" -0y100",36));
trace("// trace(parseInt(\"-0x100\")); ")
trace(parseInt("-0x100"));
trace("// trace(parseInt(\"0x-100\")); ")
trace(parseInt("0x-100"));
trace("// trace(parseInt(\" 0x-100\")); ")
trace(parseInt(" 0x-100"));
trace("// trace(parseInt(\"0x -100\")); ")
trace(parseInt("0x -100"));
trace("// trace(parseInt(\"-0100\")); ")
trace(parseInt("-0100"));
trace("// trace(parseInt(\"0-100\")); ")
trace(parseInt("0-100"));
trace("// trace(parseInt(\"+0x123\",33)); ")
trace(parseInt("+0x123",33));
trace("// trace(parseInt(\"+0x123\",34)); ")
trace(parseInt("+0x123",34));
trace("// trace(parseInt(\"0\")); ")
trace(parseInt("0"));
trace("// trace(parseInt(\" 0\")); ")
trace(parseInt(" 0"));
trace("// trace(parseInt(\" 0 \")); ")
trace(parseInt(" 0 "));
trace("// trace(parseInt(\"077\")); ")
trace(parseInt("077"));
trace("// trace(parseInt(\" 077\")); ")
trace(parseInt(" 077"));
trace("// trace(parseInt(\" 077 \")); ")
trace(parseInt(" 077 "));
trace("// trace(parseInt(\" -077\")); ")
trace(parseInt(" -077"));
trace("// trace(parseInt(\"077 \")); ")
trace(parseInt("077 "));
trace("// trace(parseInt(\"11\",2)); ")
trace(parseInt("11",2));
trace("// trace(parseInt(\"11\",3)); ")
trace(parseInt("11",3));
trace("// trace(parseInt(\"11\",3.8)); ")
trace(parseInt("11",3.8));
trace("// trace(parseInt(\"0x12\")); ")
trace(parseInt("0x12"));
trace("// trace(parseInt(\"0x12\",16)); ")
trace(parseInt("0x12",16));
trace("// trace(parseInt(\"0x12\",16.1)); ")
trace(parseInt("0x12",16.1));
trace("// trace(parseInt(\"0x12\",NaN)); ")
trace(parseInt("0x12",NaN));
trace("// trace(parseInt(\"0x \")); ")
trace(parseInt("0x "));
trace("// trace(parseInt(\"0x\")); ")
trace(parseInt("0x"));
trace("// trace(parseInt(\"0x \",16)); ")
trace(parseInt("0x ",16));
trace("// trace(parseInt(\"0x\",16)); ")
trace(parseInt("0x",16));
trace("// trace(parseInt(\"12aaa\")); ")
trace(parseInt("12aaa"));
trace("// trace(parseInt(\"100000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"000000000000000\")); ")
trace(parseInt("100000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000"));
trace("// trace(parseInt(\"0x1000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"00000000000000000000000000000000000000000000000000000000000000000000\" + \"000000000000000\")); ")
trace(parseInt("0x1000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000"));

View File

@ -0,0 +1,130 @@
// trace(parseInt());
NaN
// trace(parseInt(undefined_));
NaN
// trace(parseInt(undefined_,32));
785077
// trace(parseInt("undefined",32));
33790067563981
// trace(parseInt(""));
NaN
// trace(parseInt("123"));
123
// trace(parseInt("100",10));
100
// trace(parseInt("100",0));
100
// trace(parseInt("100",1));
NaN
// trace(parseInt("100",2));
4
// trace(parseInt("100",36));
1296
// trace(parseInt("100",37));
NaN
// trace(parseInt("100",-1));
NaN
// trace(parseInt("100",{}));
100
// trace(parseInt("100",true));
NaN
// trace(parseInt("100",false));
100
// trace(parseInt("100",NaN));
100
// trace(parseInt("100",undefined_));
100
// trace(parseInt("0x123"));
291
// trace(parseInt("0xabc"));
2748
// trace(parseInt("010",2));
2
// trace(parseInt("-0100"));
-100
// trace(parseInt("-0100z"));
-100
// trace(parseInt("0x+0X100"));
NaN
// trace(parseInt(123));
123
// trace(parseInt(123,32));
1091
// trace(parseInt("++1"));
NaN
// trace(parseInt("0x100",36));
1540944
// trace(parseInt(" 0x100",36));
1540944
// trace(parseInt("0y100",36));
1587600
// trace(parseInt(" 0y100",36));
1587600
// trace(parseInt("-0x100",36));
-1540944
// trace(parseInt(" -0x100",36));
-1540944
// trace(parseInt("-0y100",36));
-1587600
// trace(parseInt(" -0y100",36));
-1587600
// trace(parseInt("-0x100"));
-256
// trace(parseInt("0x-100"));
NaN
// trace(parseInt(" 0x-100"));
NaN
// trace(parseInt("0x -100"));
NaN
// trace(parseInt("-0100"));
-100
// trace(parseInt("0-100"));
0
// trace(parseInt("+0x123",33));
0
// trace(parseInt("+0x123",34));
1298259
// trace(parseInt("0"));
0
// trace(parseInt(" 0"));
0
// trace(parseInt(" 0 "));
0
// trace(parseInt("077"));
77
// trace(parseInt(" 077"));
77
// trace(parseInt(" 077 "));
77
// trace(parseInt(" -077"));
-77
// trace(parseInt("077 "));
77
// trace(parseInt("11",2));
3
// trace(parseInt("11",3));
4
// trace(parseInt("11",3.8));
4
// trace(parseInt("0x12"));
18
// trace(parseInt("0x12",16));
18
// trace(parseInt("0x12",16.1));
18
// trace(parseInt("0x12",NaN));
18
// trace(parseInt("0x "));
NaN
// trace(parseInt("0x"));
NaN
// trace(parseInt("0x ",16));
NaN
// trace(parseInt("0x",16));
NaN
// trace(parseInt("12aaa"));
12
// trace(parseInt("100000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000"));
Infinity
// trace(parseInt("0x1000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000"));
Infinity

Binary file not shown.