diff --git a/core/src/avm2/array.rs b/core/src/avm2/array.rs index 677573968..5106a7492 100644 --- a/core/src/avm2/array.rs +++ b/core/src/avm2/array.rs @@ -2,6 +2,15 @@ use crate::avm2::value::Value; use gc_arena::Collect; +use std::iter::ExactSizeIterator; + +/// Trait which exists purely so that we can reverse the iterators that come +/// out of `ArrayStorage.iter`. +/// +/// Not to be confused with the `ArrayIterator` struct in `globals::array`. +pub trait ArrayIterator: DoubleEndedIterator + ExactSizeIterator {} + +impl ArrayIterator for T where T: DoubleEndedIterator + ExactSizeIterator {} /// The array storage portion of an array object. /// @@ -93,7 +102,7 @@ impl<'gc> ArrayStorage<'gc> { } /// Iterate over array values. - pub fn iter<'a>(&'a self) -> impl Iterator>> + 'a { + pub fn iter<'a>(&'a self) -> impl ArrayIterator>> + 'a { self.storage.iter().cloned() } } diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 71105a3fc..7027df7c5 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -452,6 +452,64 @@ pub fn some<'gc>( Ok(Value::Undefined) } +/// Implements `Array.indexOf` +pub fn index_of<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(this) = this { + if let Some(array) = this.as_array_storage() { + let search_val = args.get(0).cloned().unwrap_or(Value::Undefined); + let from = args + .get(1) + .cloned() + .unwrap_or_else(|| 0.into()) + .coerce_to_u32(activation)?; + + for (i, val) in array.iter().enumerate() { + let val = resolve_array_hole(activation, this, i, val)?; + if i >= from as usize && val == search_val { + return Ok(i.into()); + } + } + + return Ok((-1).into()); + } + } + + Ok(Value::Undefined) +} + +/// Implements `Array.lastIndexOf` +pub fn last_index_of<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(this) = this { + if let Some(array) = this.as_array_storage() { + let search_val = args.get(0).cloned().unwrap_or(Value::Undefined); + let from = args + .get(1) + .cloned() + .unwrap_or_else(|| i32::MAX.into()) + .coerce_to_u32(activation)?; + + for (i, val) in array.iter().enumerate().rev() { + let val = resolve_array_hole(activation, this, i, val)?; + if i <= from as usize && val == search_val { + return Ok(i.into()); + } + } + + return Ok((-1).into()); + } + } + + Ok(Value::Undefined) +} + /// Construct `Array`'s class. pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { let class = Class::new( @@ -512,5 +570,15 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc> Method::from_builtin(some), )); + class.write(mc).define_instance_trait(Trait::from_method( + QName::new(Namespace::public_namespace(), "indexOf"), + Method::from_builtin(index_of), + )); + + class.write(mc).define_instance_trait(Trait::from_method( + QName::new(Namespace::public_namespace(), "lastIndexOf"), + Method::from_builtin(last_index_of), + )); + class } diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 5a85455a9..b0e68ddd6 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -369,6 +369,8 @@ swf_tests! { (as3_array_filter, "avm2/array_filter", 1), (as3_array_every, "avm2/array_every", 1), (as3_array_some, "avm2/array_some", 1), + (as3_array_indexof, "avm2/array_indexof", 1), + (as3_array_lastindexof, "avm2/array_lastindexof", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/array_indexof/Test.as b/core/tests/swfs/avm2/array_indexof/Test.as new file mode 100644 index 000000000..662078a75 --- /dev/null +++ b/core/tests/swfs/avm2/array_indexof/Test.as @@ -0,0 +1,31 @@ +package { + public class Test { + } +} + +trace("//var a = new Array(5,\"5\",3,false,4,5,undefined,9)"); +var a = new Array(5,"5",3,false,4,5,undefined,9); + +trace("//a.indexOf(5);") +trace(a.indexOf(5)); + +trace("//a.indexOf(5, 1);") +trace(a.indexOf(5, 1)); + +trace("//a.indexOf(5, 2);") +trace(a.indexOf(5, 2)); + +trace("//a.indexOf(5, 6);") +trace(a.indexOf(5, 6)); + +trace("//a.indexOf(5, 10);") +trace(a.indexOf(5, 10)); + +trace("//a.indexOf(true);"); +trace(a.indexOf(true)); + +trace("//a.indexOf(undefined);"); +trace(a.indexOf(undefined)); + +trace("//a.indexOf(\"5\");"); +trace(a.indexOf("5")); \ No newline at end of file diff --git a/core/tests/swfs/avm2/array_indexof/output.txt b/core/tests/swfs/avm2/array_indexof/output.txt new file mode 100644 index 000000000..ff292707e --- /dev/null +++ b/core/tests/swfs/avm2/array_indexof/output.txt @@ -0,0 +1,17 @@ +//var a = new Array(5,"5",3,false,4,5,undefined,9) +//a.indexOf(5); +0 +//a.indexOf(5, 1); +5 +//a.indexOf(5, 2); +5 +//a.indexOf(5, 6); +-1 +//a.indexOf(5, 10); +-1 +//a.indexOf(true); +-1 +//a.indexOf(undefined); +6 +//a.indexOf("5"); +1 diff --git a/core/tests/swfs/avm2/array_indexof/test.fla b/core/tests/swfs/avm2/array_indexof/test.fla new file mode 100644 index 000000000..b27a5e649 Binary files /dev/null and b/core/tests/swfs/avm2/array_indexof/test.fla differ diff --git a/core/tests/swfs/avm2/array_indexof/test.swf b/core/tests/swfs/avm2/array_indexof/test.swf new file mode 100644 index 000000000..3abf6ae9c Binary files /dev/null and b/core/tests/swfs/avm2/array_indexof/test.swf differ diff --git a/core/tests/swfs/avm2/array_lastindexof/Test.as b/core/tests/swfs/avm2/array_lastindexof/Test.as new file mode 100644 index 000000000..b945712c5 --- /dev/null +++ b/core/tests/swfs/avm2/array_lastindexof/Test.as @@ -0,0 +1,31 @@ +package { + public class Test { + } +} + +trace("//var a = new Array(5,\"5\",3,false,4,5,undefined,9)"); +var a = new Array(5,"5",3,false,4,5,undefined,9); + +trace("//a.lastIndexOf(5);") +trace(a.lastIndexOf(5)); + +trace("//a.lastIndexOf(5, 1);") +trace(a.lastIndexOf(5, 1)); + +trace("//a.lastIndexOf(5, 2);") +trace(a.lastIndexOf(5, 2)); + +trace("//a.lastIndexOf(5, 6);") +trace(a.lastIndexOf(5, 6)); + +trace("//a.lastIndexOf(5, 10);") +trace(a.lastIndexOf(5, 10)); + +trace("//a.lastIndexOf(true);"); +trace(a.lastIndexOf(true)); + +trace("//a.lastIndexOf(undefined);"); +trace(a.lastIndexOf(undefined)); + +trace("//a.lastIndexOf(\"5\");"); +trace(a.lastIndexOf("5")); \ No newline at end of file diff --git a/core/tests/swfs/avm2/array_lastindexof/output.txt b/core/tests/swfs/avm2/array_lastindexof/output.txt new file mode 100644 index 000000000..ea84aca37 --- /dev/null +++ b/core/tests/swfs/avm2/array_lastindexof/output.txt @@ -0,0 +1,17 @@ +//var a = new Array(5,"5",3,false,4,5,undefined,9) +//a.lastIndexOf(5); +5 +//a.lastIndexOf(5, 1); +0 +//a.lastIndexOf(5, 2); +0 +//a.lastIndexOf(5, 6); +5 +//a.lastIndexOf(5, 10); +5 +//a.lastIndexOf(true); +-1 +//a.lastIndexOf(undefined); +6 +//a.lastIndexOf("5"); +1 diff --git a/core/tests/swfs/avm2/array_lastindexof/test.fla b/core/tests/swfs/avm2/array_lastindexof/test.fla new file mode 100644 index 000000000..b27a5e649 Binary files /dev/null and b/core/tests/swfs/avm2/array_lastindexof/test.fla differ diff --git a/core/tests/swfs/avm2/array_lastindexof/test.swf b/core/tests/swfs/avm2/array_lastindexof/test.swf new file mode 100644 index 000000000..b0ee7120c Binary files /dev/null and b/core/tests/swfs/avm2/array_lastindexof/test.swf differ