avm2: Implement `Array.join`, `Array.toString`, and `Array.valueOf` (w/tests)
This commit is contained in:
parent
79df789028
commit
0eeee72be6
|
@ -91,4 +91,9 @@ impl<'gc> ArrayStorage<'gc> {
|
||||||
pub fn push(&mut self, item: Value<'gc>) {
|
pub fn push(&mut self, item: Value<'gc>) {
|
||||||
self.storage.push(Some(item))
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::avm2::class::Class;
|
||||||
use crate::avm2::method::Method;
|
use crate::avm2::method::Method;
|
||||||
use crate::avm2::names::{Namespace, QName};
|
use crate::avm2::names::{Namespace, QName};
|
||||||
use crate::avm2::object::{ArrayObject, Object, TObject};
|
use crate::avm2::object::{ArrayObject, Object, TObject};
|
||||||
|
use crate::avm2::string::AvmString;
|
||||||
use crate::avm2::traits::Trait;
|
use crate::avm2::traits::Trait;
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
use crate::avm2::Error;
|
use crate::avm2::Error;
|
||||||
|
@ -96,6 +97,70 @@ pub fn concat<'gc>(
|
||||||
.into())
|
.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.
|
/// Construct `Array`'s class.
|
||||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||||
let class = Class::new(
|
let class = Class::new(
|
||||||
|
@ -116,5 +181,20 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
||||||
Method::from_builtin(concat),
|
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
|
class
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,7 +189,7 @@ impl<'gc> TObject<'gc> for ArrayObject<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_string(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
|
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> {
|
fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
|
||||||
|
|
|
@ -361,6 +361,9 @@ swf_tests! {
|
||||||
(as3_array_holes, "avm2/array_holes", 1),
|
(as3_array_holes, "avm2/array_holes", 1),
|
||||||
(as3_array_literal, "avm2/array_literal", 1),
|
(as3_array_literal, "avm2/array_literal", 1),
|
||||||
(as3_array_concat, "avm2/array_concat", 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.
|
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.
|
||||||
|
|
|
@ -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));
|
|
@ -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.
|
@ -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());
|
|
@ -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.
|
@ -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());
|
|
@ -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.
Loading…
Reference in New Issue