From dba9b18540a0584f9ce6bd6bd7f1317e45ce1ac9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 26 Mar 2021 22:58:56 -0400 Subject: [PATCH] avm2: Impl `Vector.concat` --- core/src/avm2/globals.rs | 7 ++- core/src/avm2/globals/vector.rs | 83 ++++++++++++++++++++++++++- core/src/avm2/object/vector_object.rs | 34 +++++++++++ core/src/avm2/vector.rs | 19 ++++++ 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index bb1a09a48..828190463 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -115,6 +115,7 @@ pub struct SystemPrototypes<'gc> { pub sprite: Object<'gc>, pub simplebutton: Object<'gc>, pub regexp: Object<'gc>, + pub vector: Object<'gc>, } impl<'gc> SystemPrototypes<'gc> { @@ -164,6 +165,7 @@ impl<'gc> SystemPrototypes<'gc> { sprite: empty, simplebutton: empty, regexp: empty, + vector: empty, } } } @@ -204,6 +206,7 @@ pub struct SystemClasses<'gc> { pub sprite: Object<'gc>, pub simplebutton: Object<'gc>, pub regexp: Object<'gc>, + pub vector: Object<'gc>, } impl<'gc> SystemClasses<'gc> { @@ -253,6 +256,7 @@ impl<'gc> SystemClasses<'gc> { sprite: empty, simplebutton: empty, regexp: empty, + vector: empty, } } } @@ -499,8 +503,7 @@ pub fn load_player_globals<'gc>( class(activation, math::create_class(mc), domain, script)?; avm2_system_class!(regexp, activation, regexp::create_class(mc), domain, script); - class(activation, vector::create_class(mc), domain, script)?; - + avm2_system_class!(vector, activation, vector::create_class(mc), domain, script); avm2_system_class!(xml, activation, xml::create_class(mc), domain, script); avm2_system_class!( xml_list, diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index dd1c43774..c11b2343c 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -5,7 +5,7 @@ use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::globals::NS_VECTOR; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::names::{Namespace, QName}; -use crate::avm2::object::{vector_allocator, Object, TObject}; +use crate::avm2::object::{vector_allocator, Object, TObject, VectorObject}; use crate::avm2::value::Value; use crate::avm2::Error; use gc_arena::{GcCell, MutationContext}; @@ -84,6 +84,84 @@ pub fn set_length<'gc>( Ok(Value::Undefined) } +/// `Vector.concat` impl +pub fn concat<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(this) = this { + let mut new_vector_storage = + if let Some(vector) = this.as_vector_storage_mut(activation.context.gc_context) { + vector.clone() + } else { + return Err("Not a vector-structured object".into()); + }; + + let my_class = this + .as_class_object() + .ok_or("TypeError: Tried to concat into a bare object")?; + let val_class = new_vector_storage.value_type(); + + for arg in args.iter().map(|a| a.clone()) { + let arg_obj = arg.coerce_to_object(activation)?; + let arg_class = arg_obj + .as_class() + .ok_or("TypeError: Tried to concat from a bare object")?; + if !arg.is_of_type(activation, my_class)? { + return Err(format!( + "TypeError: Cannot coerce argument of type {:?} to argument of type {:?}", + arg_class.read().name(), + my_class + .as_class() + .ok_or("TypeError: Tried to concat into a bare object")? + .read() + .name() + ) + .into()); + } + + let old_vec = arg_obj.as_vector_storage(); + let old_vec: Vec>> = if let Some(old_vec) = old_vec { + old_vec.iter().collect() + } else { + continue; + }; + + for val in old_vec { + if let Some(val) = val { + if let Ok(val_obj) = val.coerce_to_object(activation) { + if !val.is_of_type(activation, val_class)? { + let other_val_class = val_obj + .as_class() + .ok_or("TypeError: Tried to concat a bare object into a Vector")?; + return Err(format!( + "TypeError: Cannot coerce Vector value of type {:?} to type {:?}", + other_val_class.read().name(), + val_class + .as_class() + .ok_or("TypeError: Tried to concat into a bare object")? + .read() + .name() + ) + .into()); + } + } + + let coerced_val = val.coerce_to_type(activation, val_class)?; + new_vector_storage.push(Some(coerced_val)); + } else { + new_vector_storage.push(None); + } + } + } + + return Ok(VectorObject::from_vector(new_vector_storage, activation)?.into()); + } + + Ok(Value::Undefined) +} + /// Construct `Sprite`'s class. pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { let class = Class::new( @@ -106,5 +184,8 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc> )] = &[("length", Some(length), Some(set_length))]; write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES); + const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[("concat", concat)]; + write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS); + class } diff --git a/core/src/avm2/object/vector_object.rs b/core/src/avm2/object/vector_object.rs index c4bd9ba89..1407e5b78 100644 --- a/core/src/avm2/object/vector_object.rs +++ b/core/src/avm2/object/vector_object.rs @@ -50,6 +50,40 @@ pub struct VectorObjectData<'gc> { vector: VectorStorage<'gc>, } +impl<'gc> VectorObject<'gc> { + /// Wrap an existing vector in an object. + pub fn from_vector( + vector: VectorStorage<'gc>, + activation: &mut Activation<'_, 'gc, '_>, + ) -> Result, Error> { + let value_type = vector.value_type(); + let vector_class = activation.avm2().classes().vector; + + let mut applied_class = vector_class.apply(activation, &[value_type])?; + let applied_proto = applied_class + .get_property( + applied_class, + &QName::new(Namespace::public(), "prototype"), + activation, + )? + .coerce_to_object(activation)?; + + let mut object: Object<'gc> = VectorObject(GcCell::allocate( + activation.context.gc_context, + VectorObjectData { + base: ScriptObjectData::base_new(Some(applied_proto), Some(applied_class)), + vector, + }, + )) + .into(); + + object.install_instance_traits(activation, applied_class)?; + object.call_native_init(Some(object), &[], activation, Some(applied_class))?; + + Ok(object) + } +} + impl<'gc> TObject<'gc> for VectorObject<'gc> { impl_avm2_custom_object!(base); impl_avm2_custom_object_instance!(base); diff --git a/core/src/avm2/vector.rs b/core/src/avm2/vector.rs index 726c653d1..808cb05ce 100644 --- a/core/src/avm2/vector.rs +++ b/core/src/avm2/vector.rs @@ -125,4 +125,23 @@ impl<'gc> VectorStorage<'gc> { .map(|v| *v = value) .ok_or_else(|| format!("RangeError: {} is outside the range of the vector", pos).into()) } + + /// Push a value to the end of the vector. + /// + /// This function does no coercion as calling it requires mutably borrowing + /// the vector (and thus it is unwise to reenter the AVM2 runtime to coerce + /// things). You must use the associated `coerce` fn before storing things + /// in the vector. + pub fn push(&mut self, value: Option>) { + self.storage.push(value) + } + + /// Iterate over vector values. + pub fn iter<'a>( + &'a self, + ) -> impl DoubleEndedIterator>> + + ExactSizeIterator>> + + 'a { + self.storage.iter().cloned() + } }