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);

View File

@ -419,6 +419,8 @@ pub struct FunctionObject<'gc> {
struct FunctionObjectData<'gc> {
/// The code that will be invoked when this object is called.
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`.
primitive: Value<'gc>,
@ -428,24 +430,29 @@ impl<'gc> FunctionObject<'gc> {
/// Construct a function sans prototype.
pub fn bare_function(
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>>,
) -> Self {
let base = ScriptObject::object(gc_context, fn_proto);
let func = function.map(|x| x.into());
let cons = constructor.map(|x| x.into());
FunctionObject {
base,
data: GcCell::allocate(
gc_context,
FunctionObjectData {
function: Some(function.into()),
function: func,
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
/// 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
/// `prototype` refers to the explicit prototype of the function. If
/// provided, the function and it's prototype will be linked to each other.
pub fn function(
fn allocate_function(
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>>,
prototype: Option<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 {
p.define_value(
@ -473,6 +481,47 @@ impl<'gc> FunctionObject<'gc> {
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> {
@ -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(
&self,
name: &str,
@ -547,6 +622,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
FunctionObjectData {
function: None,
primitive: "[type Function]".into(),
constructor: None,
},
),
};

View File

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

View File

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

View File

@ -9,25 +9,40 @@ use crate::context::UpdateContext;
use enumset::EnumSet;
use gc_arena::MutationContext;
/// `Boolean` constructor/function
pub fn boolean<'gc>(
/// `Boolean` constructor
pub fn constructor<'gc>(
activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let (ret_value, cons_value) = if let Some(val) = args.get(0) {
let b = Value::Bool(val.as_bool(activation.current_swf_version()));
(b.clone(), b)
let cons_value = if let Some(val) = args.get(0) {
Value::Bool(val.as_bool(activation.current_swf_version()))
} 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() {
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.
// Boolean() with no argument returns undefined.
Ok(ret_value)
@ -38,9 +53,10 @@ pub fn create_boolean_object<'gc>(
boolean_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>,
) -> Object<'gc> {
FunctionObject::function(
FunctionObject::function_and_constructor(
gc_context,
Executable::Native(boolean),
Executable::Native(boolean_function),
Executable::Native(constructor),
fn_proto,
boolean_proto,
)

View File

@ -10,7 +10,7 @@ use crate::context::UpdateContext;
use enumset::EnumSet;
use gc_arena::MutationContext;
/// `Number` constructor/function
/// `Number` constructor
pub fn number<'gc>(
activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
@ -28,6 +28,22 @@ pub fn number<'gc>(
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.
Ok(value.into())
}
@ -37,8 +53,9 @@ pub fn create_number_object<'gc>(
number_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>,
) -> Object<'gc> {
let number = FunctionObject::function(
let number = FunctionObject::function_and_constructor(
gc_context,
Executable::Native(number_function),
Executable::Native(number),
fn_proto,
number_proto,

View File

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

View File

@ -30,6 +30,22 @@ pub fn string<'gc>(
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())
}
@ -38,8 +54,9 @@ pub fn create_string_object<'gc>(
string_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>,
) -> Object<'gc> {
let string = FunctionObject::function(
let string = FunctionObject::function_and_constructor(
gc_context,
Executable::Native(string_function),
Executable::Native(string),
fn_proto,
string_proto,

View File

@ -108,6 +108,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
args: &[Value<'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.
///
/// 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.
match &value {
Value::Bool(_) => {
let _ = crate::avm1::globals::boolean::boolean(
let _ = crate::avm1::globals::boolean::constructor(
activation,
context,
obj.into(),