avm2: Implement `Array.join`, `Array.toString`, and `Array.valueOf` (w/tests)

This commit is contained in:
David Wendt 2020-08-29 15:42:08 -04:00 committed by Mike Welsh
parent 79df789028
commit 0eeee72be6
16 changed files with 217 additions and 1 deletions

View File

@ -91,4 +91,9 @@ impl<'gc> ArrayStorage<'gc> {
pub fn push(&mut self, item: Value<'gc>) {
self.storage.push(Some(item))
}
/// Iterate over array values.
pub fn iter<'a>(&'a self) -> impl Iterator<Item = Option<Value<'gc>>> + 'a {
self.storage.iter().cloned()
}
}

View File

@ -6,6 +6,7 @@ use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{ArrayObject, Object, TObject};
use crate::avm2::string::AvmString;
use crate::avm2::traits::Trait;
use crate::avm2::value::Value;
use crate::avm2::Error;
@ -96,6 +97,70 @@ pub fn concat<'gc>(
.into())
}
/// Implements `Array.join`
pub fn join<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
let mut separator = args.get(0).cloned().unwrap_or(Value::Undefined);
if separator == Value::Undefined {
separator = ",".into();
}
if let Some(this) = this {
if let Some(array) = this.as_array_storage() {
let string_separator = separator.coerce_to_string(activation)?;
let mut accum = Vec::new();
for (i, item) in array.iter().enumerate() {
let item = item.map(Ok).unwrap_or_else(|| {
this.proto()
.map(|mut p| {
p.get_property(
p,
&QName::new(
Namespace::public_namespace(),
AvmString::new(activation.context.gc_context, format!("{}", i)),
),
activation,
)
})
.unwrap_or(Ok(Value::Undefined))
});
accum.push(item?.coerce_to_string(activation)?.to_string());
}
return Ok(AvmString::new(
activation.context.gc_context,
accum.join(&string_separator),
)
.into());
}
}
Ok(Value::Undefined)
}
/// Implements `Array.toString`
pub fn to_string<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
join(activation, this, &[",".into()])
}
/// Implements `Array.valueOf`
pub fn value_of<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
join(activation, this, &[",".into()])
}
/// Construct `Array`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new(
@ -116,5 +181,20 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
Method::from_builtin(concat),
));
class.write(mc).define_instance_trait(Trait::from_method(
QName::new(Namespace::as3_namespace(), "join"),
Method::from_builtin(join),
));
class.write(mc).define_instance_trait(Trait::from_method(
QName::new(Namespace::public_namespace(), "toString"),
Method::from_builtin(to_string),
));
class.write(mc).define_instance_trait(Trait::from_method(
QName::new(Namespace::public_namespace(), "valueOf"),
Method::from_builtin(value_of),
));
class
}

View File

@ -189,7 +189,7 @@ impl<'gc> TObject<'gc> for ArrayObject<'gc> {
}
fn to_string(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
Ok("function Function() {}".into())
Ok(Value::Object(Object::from(*self)))
}
fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {

View File

@ -361,6 +361,9 @@ swf_tests! {
(as3_array_holes, "avm2/array_holes", 1),
(as3_array_literal, "avm2/array_literal", 1),
(as3_array_concat, "avm2/array_concat", 1),
(as3_array_tostring, "avm2/array_tostring", 1),
(as3_array_valueof, "avm2/array_valueof", 1),
(as3_array_join, "avm2/array_join", 1),
}
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.

View File

@ -0,0 +1,43 @@
package {
public class Test {
}
}
trace("//var a = new Array(\"a\", \"b\", \"c\");");
var a = new Array("a", "b", "c");
trace("//var b = new Array(1, 2, 3);");
var b = new Array(1, 2, 3);
trace("//var c = new Array(a, b);");
var c = new Array(a, b);
trace("//a.join();");
trace(a.join());
trace("//b.join();");
trace(b.join());
trace("//c.join();");
trace(c.join());
trace("//c.join(undefined);");
trace(c.join(undefined));
trace("//c.join(null);");
trace(c.join(null));
trace("//c.join(false);");
trace(c.join(false));
trace("//a.join(NaN);");
trace(a.join(NaN));
trace("//b.join(5);");
trace(b.join(5));
trace("//c.join(\" + \");");
trace(c.join(" + "));
trace("//c.join(b);");
trace(c.join(b));

View File

@ -0,0 +1,23 @@
//var a = new Array("a", "b", "c");
//var b = new Array(1, 2, 3);
//var c = new Array(a, b);
//a.join();
a,b,c
//b.join();
1,2,3
//c.join();
a,b,c,1,2,3
//c.join(undefined);
a,b,c,1,2,3
//c.join(null);
a,b,cnull1,2,3
//c.join(false);
a,b,cfalse1,2,3
//a.join(NaN);
aNaNbNaNc
//b.join(5);
15253
//c.join(" + ");
a,b,c + 1,2,3
//c.join(b);
a,b,c1,2,31,2,3

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,22 @@
package {
public class Test {
}
}
trace("//var a = new Array(\"a\", \"b\", \"c\");");
var a = new Array("a", "b", "c");
trace("//var b = new Array(1, 2, 3);");
var b = new Array(1, 2, 3);
trace("//var c = new Array(a, b);");
var c = new Array(a, b);
trace("//a.toString();");
trace(a.toString());
trace("//b.toString();");
trace(b.toString());
trace("//c.toString();");
trace(c.toString());

View File

@ -0,0 +1,9 @@
//var a = new Array("a", "b", "c");
//var b = new Array(1, 2, 3);
//var c = new Array(a, b);
//a.toString();
a,b,c
//b.toString();
1,2,3
//c.toString();
a,b,c,1,2,3

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,22 @@
package {
public class Test {
}
}
trace("//var a = new Array(\"a\", \"b\", \"c\");");
var a = new Array("a", "b", "c");
trace("//var b = new Array(1, 2, 3);");
var b = new Array(1, 2, 3);
trace("//var c = new Array(a, b);");
var c = new Array(a, b);
trace("//a.valueOf();");
trace(a.valueOf());
trace("//b.valueOf();");
trace(b.valueOf());
trace("//c.valueOf();");
trace(c.valueOf());

View File

@ -0,0 +1,9 @@
//var a = new Array("a", "b", "c");
//var b = new Array(1, 2, 3);
//var c = new Array(a, b);
//a.valueOf();
a,b,c
//b.valueOf();
1,2,3
//c.valueOf();
a,b,c,1,2,3

Binary file not shown.

Binary file not shown.