avm2: Implement `forEach`, `map`, `filter`, `every`, and `some` on `Array`.
This also comes with some refactoring: building the resulting array object and resolving holes is now done in helper methods.
This commit is contained in:
parent
0eeee72be6
commit
832bbdd711
|
@ -64,6 +64,25 @@ pub fn length<'gc>(
|
|||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Bundle an already-constructed `ArrayStorage` in an `Object`.
|
||||
pub fn build_array<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
array: ArrayStorage<'gc>,
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
Ok(ArrayObject::from_array(
|
||||
array,
|
||||
activation
|
||||
.context
|
||||
.avm2
|
||||
.system_prototypes
|
||||
.as_ref()
|
||||
.map(|sp| sp.array)
|
||||
.unwrap(),
|
||||
activation.context.gc_context,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Implements `Array.concat`
|
||||
#[allow(clippy::map_clone)] //You can't clone `Option<Ref<T>>` without it
|
||||
pub fn concat<'gc>(
|
||||
|
@ -83,18 +102,30 @@ pub fn concat<'gc>(
|
|||
}
|
||||
}
|
||||
|
||||
Ok(ArrayObject::from_array(
|
||||
base_array,
|
||||
activation
|
||||
.context
|
||||
.avm2
|
||||
.system_prototypes
|
||||
.as_ref()
|
||||
.map(|sp| sp.array)
|
||||
.unwrap(),
|
||||
activation.context.gc_context,
|
||||
)
|
||||
.into())
|
||||
build_array(activation, base_array)
|
||||
}
|
||||
|
||||
/// Resolves array holes.
|
||||
fn resolve_array_hole<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
i: usize,
|
||||
item: Option<Value<'gc>>,
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements `Array.join`
|
||||
|
@ -114,22 +145,9 @@ pub fn join<'gc>(
|
|||
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))
|
||||
});
|
||||
let item = resolve_array_hole(activation, this, i, item)?;
|
||||
|
||||
accum.push(item?.coerce_to_string(activation)?.to_string());
|
||||
accum.push(item.coerce_to_string(activation)?.to_string());
|
||||
}
|
||||
|
||||
return Ok(AvmString::new(
|
||||
|
@ -161,6 +179,208 @@ pub fn value_of<'gc>(
|
|||
join(activation, this, &[",".into()])
|
||||
}
|
||||
|
||||
/// Implements `Array.forEach`
|
||||
pub fn for_each<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(this) = this {
|
||||
if let Some(array) = this.as_array_storage() {
|
||||
let callback = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
.coerce_to_object(activation)?;
|
||||
let reciever = args
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null)
|
||||
.coerce_to_object(activation)
|
||||
.ok();
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
let item = resolve_array_hole(activation, this, i, item)?;
|
||||
|
||||
callback.call(
|
||||
reciever,
|
||||
&[item, i.into(), this.into()],
|
||||
activation,
|
||||
reciever.and_then(|r| r.proto()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `Array.map`
|
||||
pub fn map<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(this) = this {
|
||||
if let Some(array) = this.as_array_storage() {
|
||||
let callback = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
.coerce_to_object(activation)?;
|
||||
let reciever = args
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null)
|
||||
.coerce_to_object(activation)
|
||||
.ok();
|
||||
let mut new_array = ArrayStorage::new(0);
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
let item = resolve_array_hole(activation, this, i, item)?;
|
||||
let new_item = callback.call(
|
||||
reciever,
|
||||
&[item, i.into(), this.into()],
|
||||
activation,
|
||||
reciever.and_then(|r| r.proto()),
|
||||
)?;
|
||||
|
||||
new_array.push(new_item);
|
||||
}
|
||||
|
||||
return build_array(activation, new_array);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `Array.filter`
|
||||
pub fn filter<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(this) = this {
|
||||
if let Some(array) = this.as_array_storage() {
|
||||
let callback = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
.coerce_to_object(activation)?;
|
||||
let reciever = args
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null)
|
||||
.coerce_to_object(activation)
|
||||
.ok();
|
||||
let mut new_array = ArrayStorage::new(0);
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
let item = resolve_array_hole(activation, this, i, item)?;
|
||||
let is_allowed = callback
|
||||
.call(
|
||||
reciever,
|
||||
&[item.clone(), i.into(), this.into()],
|
||||
activation,
|
||||
reciever.and_then(|r| r.proto()),
|
||||
)?
|
||||
.coerce_to_boolean();
|
||||
|
||||
if is_allowed {
|
||||
new_array.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return build_array(activation, new_array);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `Array.every`
|
||||
pub fn every<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(this) = this {
|
||||
if let Some(array) = this.as_array_storage() {
|
||||
let callback = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
.coerce_to_object(activation)?;
|
||||
let reciever = args
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null)
|
||||
.coerce_to_object(activation)
|
||||
.ok();
|
||||
let mut is_every = true;
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
let item = resolve_array_hole(activation, this, i, item)?;
|
||||
|
||||
is_every &= callback
|
||||
.call(
|
||||
reciever,
|
||||
&[item, i.into(), this.into()],
|
||||
activation,
|
||||
reciever.and_then(|r| r.proto()),
|
||||
)?
|
||||
.coerce_to_boolean();
|
||||
}
|
||||
|
||||
return Ok(is_every.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `Array.some`
|
||||
pub fn some<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(this) = this {
|
||||
if let Some(array) = this.as_array_storage() {
|
||||
let callback = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
.coerce_to_object(activation)?;
|
||||
let reciever = args
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null)
|
||||
.coerce_to_object(activation)
|
||||
.ok();
|
||||
let mut is_some = false;
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
let item = resolve_array_hole(activation, this, i, item)?;
|
||||
|
||||
is_some |= callback
|
||||
.call(
|
||||
reciever,
|
||||
&[item, i.into(), this.into()],
|
||||
activation,
|
||||
reciever.and_then(|r| r.proto()),
|
||||
)?
|
||||
.coerce_to_boolean();
|
||||
}
|
||||
|
||||
return Ok(is_some.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Construct `Array`'s class.
|
||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||
let class = Class::new(
|
||||
|
@ -196,5 +416,30 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
|||
Method::from_builtin(value_of),
|
||||
));
|
||||
|
||||
class.write(mc).define_instance_trait(Trait::from_method(
|
||||
QName::new(Namespace::public_namespace(), "forEach"),
|
||||
Method::from_builtin(for_each),
|
||||
));
|
||||
|
||||
class.write(mc).define_instance_trait(Trait::from_method(
|
||||
QName::new(Namespace::public_namespace(), "map"),
|
||||
Method::from_builtin(map),
|
||||
));
|
||||
|
||||
class.write(mc).define_instance_trait(Trait::from_method(
|
||||
QName::new(Namespace::public_namespace(), "filter"),
|
||||
Method::from_builtin(filter),
|
||||
));
|
||||
|
||||
class.write(mc).define_instance_trait(Trait::from_method(
|
||||
QName::new(Namespace::public_namespace(), "every"),
|
||||
Method::from_builtin(every),
|
||||
));
|
||||
|
||||
class.write(mc).define_instance_trait(Trait::from_method(
|
||||
QName::new(Namespace::public_namespace(), "some"),
|
||||
Method::from_builtin(some),
|
||||
));
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -364,6 +364,11 @@ swf_tests! {
|
|||
(as3_array_tostring, "avm2/array_tostring", 1),
|
||||
(as3_array_valueof, "avm2/array_valueof", 1),
|
||||
(as3_array_join, "avm2/array_join", 1),
|
||||
(as3_array_foreach, "avm2/array_foreach", 1),
|
||||
(as3_array_map, "avm2/array_map", 1),
|
||||
(as3_array_filter, "avm2/array_filter", 1),
|
||||
(as3_array_every, "avm2/array_every", 1),
|
||||
(as3_array_some, "avm2/array_some", 1),
|
||||
}
|
||||
|
||||
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package {
|
||||
public class Test {
|
||||
}
|
||||
}
|
||||
|
||||
function assert_array(a) {
|
||||
for (var i = 0; i < a.length; i += 1) {
|
||||
trace(a[i]);
|
||||
}
|
||||
}
|
||||
|
||||
trace("//var a = new Array(5,3,1,9,16)");
|
||||
var a = new Array(5,3,1,9,16);
|
||||
|
||||
trace("//trace(a.every(function (val) { return val === 5; }));");
|
||||
trace(a.every(function (val) {
|
||||
return val === 5;
|
||||
}));
|
||||
|
||||
trace("//trace(a.every(function (val) { return val !== 20; }));");
|
||||
trace(a.every(function (val) {
|
||||
return val !== 20;
|
||||
}));
|
||||
|
||||
trace("//var b = new Array();");
|
||||
var b = new Array();
|
||||
|
||||
trace("//trace(b.every(function (val) { return val === 5; }));");
|
||||
trace(b.every(function (val) {
|
||||
return val === 5;
|
||||
}));
|
|
@ -0,0 +1,8 @@
|
|||
//var a = new Array(5,3,1,9,16)
|
||||
//trace(a.every(function (val) { return val === 5; }));
|
||||
false
|
||||
//trace(a.every(function (val) { return val !== 20; }));
|
||||
true
|
||||
//var b = new Array();
|
||||
//trace(b.every(function (val) { return val === 5; }));
|
||||
true
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,21 @@
|
|||
package {
|
||||
public class Test {
|
||||
}
|
||||
}
|
||||
|
||||
function assert_array(a) {
|
||||
for (var i = 0; i < a.length; i += 1) {
|
||||
trace(a[i]);
|
||||
}
|
||||
}
|
||||
|
||||
trace("//var a = new Array(5,3,1,9,16)");
|
||||
var a = new Array(5,3,1,9,16);
|
||||
|
||||
trace("//var b = a.filter(function (val) { ... });");
|
||||
var b = a.filter(function (val) {
|
||||
return val <= 5;
|
||||
});
|
||||
|
||||
trace("//(contents of b)");
|
||||
assert_array(b);
|
|
@ -0,0 +1,6 @@
|
|||
//var a = new Array(5,3,1,9,16)
|
||||
//var b = a.filter(function (val) { ... });
|
||||
//(contents of b)
|
||||
5
|
||||
3
|
||||
1
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,19 @@
|
|||
package {
|
||||
public class Test {
|
||||
}
|
||||
}
|
||||
|
||||
trace("//var a = new Array(5,\"abc\")");
|
||||
var a = new Array(5,"abc");
|
||||
|
||||
trace("//a.forEach(function (val) { ... }, a);");
|
||||
a.forEach(function (val, index, array) {
|
||||
trace("//(in callback) this === a;")
|
||||
trace(this === a);
|
||||
trace("//val");
|
||||
trace(val);
|
||||
trace("//index");
|
||||
trace(index);
|
||||
trace("//array === a");
|
||||
trace(array === a);
|
||||
}, a);
|
|
@ -0,0 +1,18 @@
|
|||
//var a = new Array(5,"abc")
|
||||
//a.forEach(function (val) { ... }, a);
|
||||
//(in callback) this === a;
|
||||
true
|
||||
//val
|
||||
5
|
||||
//index
|
||||
0
|
||||
//array === a
|
||||
true
|
||||
//(in callback) this === a;
|
||||
true
|
||||
//val
|
||||
abc
|
||||
//index
|
||||
1
|
||||
//array === a
|
||||
true
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,21 @@
|
|||
package {
|
||||
public class Test {
|
||||
}
|
||||
}
|
||||
|
||||
function assert_array(a) {
|
||||
for (var i = 0; i < a.length; i += 1) {
|
||||
trace(a[i]);
|
||||
}
|
||||
}
|
||||
|
||||
trace("//var a = new Array(5,3,1,9,16)");
|
||||
var a = new Array(5,3,1,9,16);
|
||||
|
||||
trace("//var b = a.map(function (val) { return val + 1; });");
|
||||
var b = a.map(function (val) {
|
||||
return val + 1;
|
||||
});
|
||||
|
||||
trace("//(contents of b)");
|
||||
assert_array(b);
|
|
@ -0,0 +1,8 @@
|
|||
//var a = new Array(5,3,1,9,16)
|
||||
//var b = a.map(function (val) { return val + 1; });
|
||||
//(contents of b)
|
||||
6
|
||||
4
|
||||
2
|
||||
10
|
||||
17
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,31 @@
|
|||
package {
|
||||
public class Test {
|
||||
}
|
||||
}
|
||||
|
||||
function assert_array(a) {
|
||||
for (var i = 0; i < a.length; i += 1) {
|
||||
trace(a[i]);
|
||||
}
|
||||
}
|
||||
|
||||
trace("//var a = new Array(5,3,1,9,16)");
|
||||
var a = new Array(5,3,1,9,16);
|
||||
|
||||
trace("//trace(a.some(function (val) { return val === 5; }));");
|
||||
trace(a.some(function (val) {
|
||||
return val === 5;
|
||||
}));
|
||||
|
||||
trace("//trace(a.some(function (val) { return val === 20; }));");
|
||||
trace(a.some(function (val) {
|
||||
return val === 20;
|
||||
}));
|
||||
|
||||
trace("//var b = new Array();");
|
||||
var b = new Array();
|
||||
|
||||
trace("//trace(b.some(function (val) { return val === 20; }));");
|
||||
trace(b.some(function (val) {
|
||||
return val === 20;
|
||||
}));
|
|
@ -0,0 +1,8 @@
|
|||
//var a = new Array(5,3,1,9,16)
|
||||
//trace(a.some(function (val) { return val === 5; }));
|
||||
true
|
||||
//trace(a.some(function (val) { return val === 20; }));
|
||||
false
|
||||
//var b = new Array();
|
||||
//trace(b.some(function (val) { return val === 20; }));
|
||||
false
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue