ruffle/core/src/avm1/scope.rs

308 lines
9.3 KiB
Rust
Raw Normal View History

//! Represents AVM1 scope chain resolution.
use crate::avm1::{ActionContext, Avm1, Object, Value};
use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext};
use std::cell::{Ref, RefMut};
2019-09-23 01:10:49 +00:00
/// Indicates what kind of scope a scope is.
#[derive(Copy, Clone, Debug, PartialEq)]
2019-09-23 01:10:49 +00:00
pub enum ScopeClass {
/// Scope represents global scope.
Global,
/// Target represents timeline scope. All timeline actions execute with
/// the current clip object in lieu of a local scope, and the timeline scope
/// can be changed via `tellTarget`.
Target,
2019-09-23 01:10:49 +00:00
/// Scope represents local scope and is inherited when a closure is defined.
Local,
/// Scope represents an object added to the scope chain with `with`.
/// It is not inherited when closures are defined.
With,
2019-09-23 01:10:49 +00:00
}
/// Represents a scope chain for an AVM1 activation.
#[derive(Debug)]
pub struct Scope<'gc> {
parent: Option<GcCell<'gc, Scope<'gc>>>,
2019-09-23 01:10:49 +00:00
class: ScopeClass,
values: GcCell<'gc, Object<'gc>>,
}
unsafe impl<'gc> gc_arena::Collect for Scope<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
self.parent.trace(cc);
self.values.trace(cc);
}
}
impl<'gc> Scope<'gc> {
/// Construct a global scope (one without a parent).
pub fn from_global_object(globals: GcCell<'gc, Object<'gc>>) -> Scope<'gc> {
Scope {
parent: None,
2019-09-23 01:10:49 +00:00
class: ScopeClass::Global,
values: globals,
}
}
2019-09-19 00:47:21 +00:00
/// Construct a child scope of another scope.
2019-09-23 01:10:49 +00:00
pub fn new_local_scope(parent: GcCell<'gc, Self>, mc: MutationContext<'gc, '_>) -> Scope<'gc> {
Scope {
parent: Some(parent),
2019-09-23 01:10:49 +00:00
class: ScopeClass::Local,
values: GcCell::allocate(mc, Object::bare_object()),
}
}
2019-09-23 01:10:49 +00:00
/// Construct a closure scope to be used as the parent of all local scopes
/// when invoking a function.
pub fn new_closure_scope(
mut parent: GcCell<'gc, Self>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
2019-09-23 01:10:49 +00:00
let mut closure_scope_list = Vec::new();
loop {
if parent.read().class != ScopeClass::With {
closure_scope_list.push(parent);
2019-09-23 01:10:49 +00:00
}
let grandparent = parent.read().parent;
2019-09-23 01:10:49 +00:00
if let Some(grandparent) = grandparent {
parent = grandparent;
} else {
break;
}
}
let mut parent_scope = None;
for scope in closure_scope_list.iter().rev() {
parent_scope = Some(GcCell::allocate(
mc,
Scope {
parent: parent_scope,
class: scope.read().class,
values: scope.read().values,
},
));
2019-09-23 01:10:49 +00:00
}
if let Some(parent_scope) = parent_scope {
parent_scope
} else {
GcCell::allocate(
mc,
Scope {
parent: None,
class: ScopeClass::Global,
values: GcCell::allocate(mc, Object::bare_object()),
},
)
2019-09-23 01:10:49 +00:00
}
}
/// Construct a scope for use with `tellTarget` code where the timeline
/// scope has been replaced with another given object.
pub fn new_target_scope(
mut parent: GcCell<'gc, Self>,
clip: GcCell<'gc, Object<'gc>>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
let mut timeline_scope_list = Vec::new();
loop {
if parent.read().class != ScopeClass::Target {
timeline_scope_list.push(parent);
} else {
let new_scope = Self {
parent: None,
class: ScopeClass::Target,
values: clip,
};
timeline_scope_list.push(GcCell::allocate(mc, new_scope));
}
let grandparent = parent.read().parent;
if let Some(grandparent) = grandparent {
parent = grandparent;
} else {
break;
}
}
let mut parent_scope = None;
for scope in timeline_scope_list.iter().rev() {
parent_scope = Some(GcCell::allocate(
mc,
Scope {
parent: parent_scope,
class: scope.read().class,
values: scope.read().values,
},
));
}
if let Some(parent_scope) = parent_scope {
parent_scope
} else {
GcCell::allocate(
mc,
Scope {
parent: None,
class: ScopeClass::Global,
values: GcCell::allocate(mc, Object::bare_object()),
},
)
}
}
/// Construct a with scope to be used as the scope during a with block.
///
/// A with block inserts the values of a particular object into the scope
/// of currently running code, while still maintaining the same local
/// scope. This requires some scope chain juggling.
pub fn new_with_scope(
locals: GcCell<'gc, Self>,
with_object: GcCell<'gc, Object<'gc>>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
let parent_scope = locals.read().parent;
let local_values = locals.read().values;
let with_scope = GcCell::allocate(
mc,
Scope {
parent: parent_scope,
class: ScopeClass::With,
values: with_object,
},
);
GcCell::allocate(
mc,
Scope {
parent: Some(with_scope),
class: ScopeClass::Local,
values: local_values,
},
)
}
2019-09-23 01:10:49 +00:00
/// Construct an arbitrary scope
pub fn new(
parent: GcCell<'gc, Self>,
class: ScopeClass,
with_object: GcCell<'gc, Object<'gc>>,
) -> Scope<'gc> {
2019-09-19 00:47:21 +00:00
Scope {
parent: Some(parent),
class,
values: with_object,
2019-09-19 00:47:21 +00:00
}
}
/// Returns a reference to the current local scope object.
pub fn locals(&self) -> Ref<Object<'gc>> {
self.values.read()
}
2019-10-08 14:34:08 +00:00
/// Returns a gc cell of the current local scope object.
pub fn locals_cell(&self) -> GcCell<'gc, Object<'gc>> {
self.values.to_owned()
}
2019-09-22 01:41:30 +00:00
/// Returns a reference to the current local scope object for mutation.
pub fn locals_mut(&self, mc: MutationContext<'gc, '_>) -> RefMut<Object<'gc>> {
self.values.write(mc)
}
/// Returns a reference to the parent scope object.
pub fn parent(&self) -> Option<Ref<Scope<'gc>>> {
match self.parent {
Some(ref p) => Some(p.read()),
None => None,
}
}
/// Resolve a particular value in the scope chain.
pub fn resolve(&self, name: &str) -> Value<'gc> {
if self.locals().has_property(name) {
2019-09-22 01:41:30 +00:00
return self.locals().force_get(name);
}
if let Some(scope) = self.parent() {
return scope.resolve(name);
}
Value::Undefined
}
/// Check if a particular property in the scope chain is defined.
pub fn is_defined(&self, name: &str) -> bool {
if self.locals().has_property(name) {
return true;
}
if let Some(scope) = self.parent() {
return scope.is_defined(name);
}
false
}
2019-09-22 01:41:30 +00:00
/// Update a particular value in the scope chain, but only if it was
/// previously defined.
///
2019-09-22 01:41:30 +00:00
/// If the value is currently already defined in this scope, then it will
/// be overwritten. If it is not defined, then we traverse the scope chain
/// until we find a defined value to overwrite. We do not define a property
/// if it is not already defined somewhere in the scope chain, and instead
/// return it so that the caller may manually define the property itself.
pub fn overwrite(
&self,
name: &str,
value: Value<'gc>,
avm: &mut Avm1<'gc>,
context: &mut ActionContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>,
) -> Option<Value<'gc>> {
2019-09-22 01:41:30 +00:00
if self.locals().has_property(name) {
self.locals_mut(context.gc_context)
.set(name, value, avm, context, this);
2019-09-22 01:41:30 +00:00
return None;
}
if let Some(scope) = self.parent() {
return scope.overwrite(name, value, avm, context, this);
2019-09-22 01:41:30 +00:00
}
Some(value)
}
/// Set a particular value in the locals for this scope.
///
/// By convention, the locals for a given function are always defined as
/// stored (e.g. not virtual) properties on the lowest object in the scope
/// chain. As a result, this function always force sets a property on the
/// local object and does not traverse the scope chain.
pub fn define(&self, name: &str, value: Value<'gc>, mc: MutationContext<'gc, '_>) {
self.locals_mut(mc).force_set(name, value, EnumSet::empty());
}
/// Delete a value from scope
pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) -> bool {
if self.locals().has_property(name) {
return self.locals_mut(mc).delete(name);
}
if let Some(scope) = self.parent() {
return scope.delete(name, mc);
}
false
}
}