core: Split construct and call for function objects

This commit is contained in:
CUB3D 2020-07-26 01:52:25 +01:00 committed by Mike Welsh
parent 337e3292dd
commit e83dbf7327
10 changed files with 176 additions and 37 deletions

View File

@ -1799,7 +1799,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
); );
} }
constructor.call("[ctor]", self, context, this, None, &args)?; constructor.construct("[ctor]", self, context, this, None, &args)?;
self.avm.push(this); self.avm.push(this);

View File

@ -419,6 +419,8 @@ pub struct FunctionObject<'gc> {
struct FunctionObjectData<'gc> { struct FunctionObjectData<'gc> {
/// The code that will be invoked when this object is called. /// The code that will be invoked when this object is called.
function: Option<Executable<'gc>>, function: Option<Executable<'gc>>,
/// The code that will be invoked when this object is constructed.
constructor: Option<Executable<'gc>>,
/// The value to be returned by `toString` and `valueOf`. /// The value to be returned by `toString` and `valueOf`.
primitive: Value<'gc>, primitive: Value<'gc>,
@ -428,24 +430,29 @@ impl<'gc> FunctionObject<'gc> {
/// Construct a function sans prototype. /// Construct a function sans prototype.
pub fn bare_function( pub fn bare_function(
gc_context: MutationContext<'gc, '_>, gc_context: MutationContext<'gc, '_>,
function: impl Into<Executable<'gc>>, function: Option<impl Into<Executable<'gc>>>,
constructor: Option<impl Into<Executable<'gc>>>,
fn_proto: Option<Object<'gc>>, fn_proto: Option<Object<'gc>>,
) -> Self { ) -> Self {
let base = ScriptObject::object(gc_context, fn_proto); let base = ScriptObject::object(gc_context, fn_proto);
let func = function.map(|x| x.into());
let cons = constructor.map(|x| x.into());
FunctionObject { FunctionObject {
base, base,
data: GcCell::allocate( data: GcCell::allocate(
gc_context, gc_context,
FunctionObjectData { FunctionObjectData {
function: Some(function.into()), function: func,
primitive: "[type Function]".into(), primitive: "[type Function]".into(),
constructor: cons,
}, },
), ),
} }
} }
/// Construct a function from an executable and associated protos. /// Construct a function with any combination of regular and constructor parts.
/// ///
/// Since prototypes need to link back to themselves, this function builds /// Since prototypes need to link back to themselves, this function builds
/// both objects itself and returns the function to you, fully allocated. /// both objects itself and returns the function to you, fully allocated.
@ -453,13 +460,14 @@ impl<'gc> FunctionObject<'gc> {
/// `fn_proto` refers to the implicit proto of the function object, and the /// `fn_proto` refers to the implicit proto of the function object, and the
/// `prototype` refers to the explicit prototype of the function. If /// `prototype` refers to the explicit prototype of the function. If
/// provided, the function and it's prototype will be linked to each other. /// provided, the function and it's prototype will be linked to each other.
pub fn function( fn allocate_function(
context: MutationContext<'gc, '_>, context: MutationContext<'gc, '_>,
function: impl Into<Executable<'gc>>, function: Option<impl Into<Executable<'gc>>>,
constructor: Option<impl Into<Executable<'gc>>>,
fn_proto: Option<Object<'gc>>, fn_proto: Option<Object<'gc>>,
prototype: Option<Object<'gc>>, prototype: Option<Object<'gc>>,
) -> Object<'gc> { ) -> Object<'gc> {
let function = Self::bare_function(context, function, fn_proto).into(); let function = Self::bare_function(context, function, constructor, fn_proto).into();
if let Some(p) = prototype { if let Some(p) = prototype {
p.define_value( p.define_value(
@ -473,6 +481,47 @@ impl<'gc> FunctionObject<'gc> {
function function
} }
/// Construct a regular function from an executable and associated protos.
pub fn function(
context: MutationContext<'gc, '_>,
function: impl Into<Executable<'gc>>,
fn_proto: Option<Object<'gc>>,
prototype: Option<Object<'gc>>,
) -> Object<'gc> {
// Avoid type inference issues
let none: Option<Executable> = None;
Self::allocate_function(context, Some(function), none, fn_proto, prototype)
}
/// Construct a constructor function from an executable and associated protos.
pub fn constructor(
context: MutationContext<'gc, '_>,
constructor: impl Into<Executable<'gc>>,
fn_proto: Option<Object<'gc>>,
prototype: Option<Object<'gc>>,
) -> Object<'gc> {
// Avoid type inference issues
let none: Option<Executable> = None;
Self::allocate_function(context, none, Some(constructor), fn_proto, prototype)
}
/// Construct a regular and constructor function from an executable and associated protos.
pub fn function_and_constructor(
context: MutationContext<'gc, '_>,
function: impl Into<Executable<'gc>>,
constructor: impl Into<Executable<'gc>>,
fn_proto: Option<Object<'gc>>,
prototype: Option<Object<'gc>>,
) -> Object<'gc> {
Self::allocate_function(
context,
Some(function),
Some(constructor),
fn_proto,
prototype,
)
}
} }
impl<'gc> TObject<'gc> for FunctionObject<'gc> { impl<'gc> TObject<'gc> for FunctionObject<'gc> {
@ -521,6 +570,32 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
} }
} }
fn construct(
&self,
name: &str,
activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
base_proto: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
println!("Constructing a function {}: {:?}", name, &self.data.read());
if let Some(exec) = &self.data.read().constructor {
exec.exec(
name,
activation,
context,
this,
base_proto,
args,
ExecutionReason::FunctionCall,
(*self).into(),
)
} else {
self.call(name, activation, context, this, base_proto, args)
}
}
fn call_setter( fn call_setter(
&self, &self,
name: &str, name: &str,
@ -547,6 +622,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
FunctionObjectData { FunctionObjectData {
function: None, function: None,
primitive: "[type Function]".into(), primitive: "[type Function]".into(),
constructor: None,
}, },
), ),
}; };

View File

@ -282,74 +282,74 @@ pub fn create_globals<'gc>(
let context_menu_item_proto = let context_menu_item_proto =
context_menu_item::create_proto(gc_context, object_proto, function_proto); context_menu_item::create_proto(gc_context, object_proto, function_proto);
let button = FunctionObject::function( let button = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(button::constructor), Executable::Native(button::constructor),
Some(function_proto), Some(function_proto),
Some(button_proto), Some(button_proto),
); );
let color = FunctionObject::function( let color = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(color::constructor), Executable::Native(color::constructor),
Some(function_proto), Some(function_proto),
Some(color_proto), Some(color_proto),
); );
let error = FunctionObject::function( let error = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(error::constructor), Executable::Native(error::constructor),
Some(function_proto), Some(function_proto),
Some(error_proto), Some(error_proto),
); );
let function = FunctionObject::function( let function = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(function::constructor), Executable::Native(function::constructor),
Some(function_proto), Some(function_proto),
Some(function_proto), Some(function_proto),
); );
let load_vars = FunctionObject::function( let load_vars = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(load_vars::constructor), Executable::Native(load_vars::constructor),
Some(function_proto), Some(function_proto),
Some(load_vars_proto), Some(load_vars_proto),
); );
let movie_clip = FunctionObject::function( let movie_clip = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(movie_clip::constructor), Executable::Native(movie_clip::constructor),
Some(function_proto), Some(function_proto),
Some(movie_clip_proto), Some(movie_clip_proto),
); );
let movie_clip_loader = FunctionObject::function( let movie_clip_loader = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(movie_clip_loader::constructor), Executable::Native(movie_clip_loader::constructor),
Some(function_proto), Some(function_proto),
Some(movie_clip_loader_proto), Some(movie_clip_loader_proto),
); );
let sound = FunctionObject::function( let sound = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(sound::constructor), Executable::Native(sound::constructor),
Some(function_proto), Some(function_proto),
Some(sound_proto), Some(sound_proto),
); );
let text_field = FunctionObject::function( let text_field = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(text_field::constructor), Executable::Native(text_field::constructor),
Some(function_proto), Some(function_proto),
Some(text_field_proto), Some(text_field_proto),
); );
let text_format = FunctionObject::function( let text_format = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(text_format::constructor), Executable::Native(text_format::constructor),
Some(function_proto), Some(function_proto),
Some(text_format_proto), Some(text_format_proto),
); );
let array = array::create_array_object(gc_context, Some(array_proto), Some(function_proto)); let array = array::create_array_object(gc_context, Some(array_proto), Some(function_proto));
let xmlnode = FunctionObject::function( let xmlnode = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(xml::xmlnode_constructor), Executable::Native(xml::xmlnode_constructor),
Some(function_proto), Some(function_proto),
Some(xmlnode_proto), Some(xmlnode_proto),
); );
let xml = FunctionObject::function( let xml = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(xml::xml_constructor), Executable::Native(xml::xml_constructor),
Some(function_proto), Some(function_proto),
@ -434,7 +434,7 @@ pub fn create_globals<'gc>(
globals.define_value( globals.define_value(
gc_context, gc_context,
"ContextMenu", "ContextMenu",
FunctionObject::function( FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(context_menu::constructor), Executable::Native(context_menu::constructor),
Some(function_proto), Some(function_proto),
@ -447,7 +447,7 @@ pub fn create_globals<'gc>(
globals.define_value( globals.define_value(
gc_context, gc_context,
"ContextMenuItem", "ContextMenuItem",
FunctionObject::function( FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(context_menu_item::constructor), Executable::Native(context_menu_item::constructor),
Some(function_proto), Some(function_proto),

View File

@ -38,7 +38,7 @@ pub fn create_array_object<'gc>(
array_proto: Option<Object<'gc>>, array_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>, fn_proto: Option<Object<'gc>>,
) -> Object<'gc> { ) -> Object<'gc> {
let array = FunctionObject::function( let array = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(constructor), Executable::Native(constructor),
fn_proto, fn_proto,
@ -87,7 +87,7 @@ pub fn create_array_object<'gc>(
array array
} }
/// Implements `Array` /// Implements `Array` constructor
pub fn constructor<'gc>( pub fn constructor<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,

View File

@ -9,25 +9,40 @@ use crate::context::UpdateContext;
use enumset::EnumSet; use enumset::EnumSet;
use gc_arena::MutationContext; use gc_arena::MutationContext;
/// `Boolean` constructor/function /// `Boolean` constructor
pub fn boolean<'gc>( pub fn constructor<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let (ret_value, cons_value) = if let Some(val) = args.get(0) { let cons_value = if let Some(val) = args.get(0) {
let b = Value::Bool(val.as_bool(activation.current_swf_version())); Value::Bool(val.as_bool(activation.current_swf_version()))
(b.clone(), b)
} else { } else {
(Value::Undefined, Value::Bool(false)) Value::Bool(false)
}; };
// If called from a constructor, populate `this`. // Called from a constructor, populate `this`.
if let Some(mut vbox) = this.as_value_object() { if let Some(mut vbox) = this.as_value_object() {
vbox.replace_value(context.gc_context, cons_value); vbox.replace_value(context.gc_context, cons_value);
} }
Ok(Value::Undefined)
}
/// `Boolean` function
pub fn boolean_function<'gc>(
activation: &mut Activation<'_, 'gc>,
_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let ret_value = if let Some(val) = args.get(0) {
Value::Bool(val.as_bool(activation.current_swf_version()))
} else {
Value::Undefined
};
// If called as a function, return the value. // If called as a function, return the value.
// Boolean() with no argument returns undefined. // Boolean() with no argument returns undefined.
Ok(ret_value) Ok(ret_value)
@ -38,9 +53,10 @@ pub fn create_boolean_object<'gc>(
boolean_proto: Option<Object<'gc>>, boolean_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>, fn_proto: Option<Object<'gc>>,
) -> Object<'gc> { ) -> Object<'gc> {
FunctionObject::function( FunctionObject::function_and_constructor(
gc_context, gc_context,
Executable::Native(boolean), Executable::Native(boolean_function),
Executable::Native(constructor),
fn_proto, fn_proto,
boolean_proto, boolean_proto,
) )

View File

@ -10,7 +10,7 @@ use crate::context::UpdateContext;
use enumset::EnumSet; use enumset::EnumSet;
use gc_arena::MutationContext; use gc_arena::MutationContext;
/// `Number` constructor/function /// `Number` constructor
pub fn number<'gc>( pub fn number<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
@ -28,6 +28,22 @@ pub fn number<'gc>(
vbox.replace_value(context.gc_context, value.into()); vbox.replace_value(context.gc_context, value.into());
} }
Ok(Value::Undefined)
}
/// `Number` function
pub fn number_function<'gc>(
activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let value = if let Some(val) = args.get(0) {
val.coerce_to_f64(activation, context)?
} else {
0.0
};
// If Number is called as a function, return the value. // If Number is called as a function, return the value.
Ok(value.into()) Ok(value.into())
} }
@ -37,8 +53,9 @@ pub fn create_number_object<'gc>(
number_proto: Option<Object<'gc>>, number_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>, fn_proto: Option<Object<'gc>>,
) -> Object<'gc> { ) -> Object<'gc> {
let number = FunctionObject::function( let number = FunctionObject::function_and_constructor(
gc_context, gc_context,
Executable::Native(number_function),
Executable::Native(number), Executable::Native(number),
fn_proto, fn_proto,
number_proto, number_proto,

View File

@ -372,7 +372,7 @@ pub fn create_object_object<'gc>(
proto: Object<'gc>, proto: Object<'gc>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) -> Object<'gc> { ) -> Object<'gc> {
let object_function = FunctionObject::function( let object_function = FunctionObject::constructor(
gc_context, gc_context,
Executable::Native(constructor), Executable::Native(constructor),
Some(fn_proto), Some(fn_proto),

View File

@ -30,6 +30,22 @@ pub fn string<'gc>(
vbox.replace_value(ac.gc_context, value.clone().into()); vbox.replace_value(ac.gc_context, value.clone().into());
} }
Ok(Value::Undefined)
}
/// `String` function
pub fn string_function<'gc>(
activation: &mut Activation<'_, 'gc>,
ac: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let value = match args.get(0).cloned() {
Some(Value::String(s)) => s,
Some(v) => v.coerce_to_string(activation, ac)?,
_ => AvmString::new(ac.gc_context, String::new()),
};
Ok(value.into()) Ok(value.into())
} }
@ -38,8 +54,9 @@ pub fn create_string_object<'gc>(
string_proto: Option<Object<'gc>>, string_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>, fn_proto: Option<Object<'gc>>,
) -> Object<'gc> { ) -> Object<'gc> {
let string = FunctionObject::function( let string = FunctionObject::function_and_constructor(
gc_context, gc_context,
Executable::Native(string_function),
Executable::Native(string), Executable::Native(string),
fn_proto, fn_proto,
string_proto, string_proto,

View File

@ -108,6 +108,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>>; ) -> Result<Value<'gc>, Error<'gc>>;
/// Construct the underlying object.
fn construct(
&self,
_name: &str,
_activation: &mut Activation<'_, 'gc>,
_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_base_proto: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(Value::Undefined)
}
/// Call a method on the object. /// Call a method on the object.
/// ///
/// It is highly recommended to use this convenience method to perform /// It is highly recommended to use this convenience method to perform

View File

@ -62,7 +62,7 @@ impl<'gc> ValueObject<'gc> {
// Constructor populates the boxed object with the value. // Constructor populates the boxed object with the value.
match &value { match &value {
Value::Bool(_) => { Value::Bool(_) => {
let _ = crate::avm1::globals::boolean::boolean( let _ = crate::avm1::globals::boolean::constructor(
activation, activation,
context, context,
obj.into(), obj.into(),