2020-02-10 19:54:55 +00:00
|
|
|
//! Represents AVM2 scope chain resolution.
|
|
|
|
|
2020-02-21 19:27:50 +00:00
|
|
|
use crate::avm2::names::Multiname;
|
2020-02-10 19:54:55 +00:00
|
|
|
use crate::avm2::object::{Object, TObject};
|
|
|
|
use crate::avm2::return_value::ReturnValue;
|
|
|
|
use crate::avm2::value::Value;
|
|
|
|
use crate::avm2::{Avm2, Error};
|
|
|
|
use crate::context::UpdateContext;
|
|
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
|
|
|
use std::cell::Ref;
|
|
|
|
|
|
|
|
/// Indicates what kind of scope a scope is.
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub enum ScopeClass {
|
|
|
|
/// Scope represents global or closure scope.
|
|
|
|
GlobalOrClosure,
|
|
|
|
|
|
|
|
/// Scope represents an object added to the scope chain with `with`.
|
|
|
|
/// It is not inherited when closures are defined. Furthermore, a `with`
|
|
|
|
/// scope gains the ability to be searched for dynamic properties.
|
|
|
|
With,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a scope chain for an AVM2 activation.
|
|
|
|
#[derive(Debug, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Scope<'gc> {
|
|
|
|
parent: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
class: ScopeClass,
|
|
|
|
values: Object<'gc>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Scope<'gc> {
|
|
|
|
/// Push a scope onto the stack, producing a new scope chain that's one
|
|
|
|
/// item longer.
|
|
|
|
pub fn push_scope(
|
|
|
|
scope_stack: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
object: Object<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> GcCell<'gc, Self> {
|
|
|
|
GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
Self {
|
|
|
|
parent: scope_stack,
|
|
|
|
class: ScopeClass::GlobalOrClosure,
|
|
|
|
values: object,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Construct a with scope to be used as the scope during a with block.
|
|
|
|
///
|
|
|
|
/// A with block adds an object to the top of the scope chain, so unqualified
|
|
|
|
/// references will try to resolve on that object first.
|
|
|
|
pub fn push_with(
|
|
|
|
scope_stack: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
with_object: Object<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> GcCell<'gc, Self> {
|
|
|
|
GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
Scope {
|
|
|
|
parent: scope_stack,
|
|
|
|
class: ScopeClass::With,
|
|
|
|
values: with_object,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-02-11 19:46:47 +00:00
|
|
|
pub fn pop_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
|
|
|
|
self.parent
|
|
|
|
}
|
|
|
|
|
2020-02-10 19:54:55 +00:00
|
|
|
/// Returns a reference to the current local scope object.
|
|
|
|
pub fn locals(&self) -> &Object<'gc> {
|
|
|
|
&self.values
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a reference to the current local scope object for mutation.
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub fn locals_mut(&mut self) -> &mut Object<'gc> {
|
|
|
|
&mut self.values
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-22 04:31:30 +00:00
|
|
|
pub fn parent_cell(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
|
|
|
|
self.parent
|
|
|
|
}
|
|
|
|
|
2020-02-11 04:28:46 +00:00
|
|
|
/// Find an object that contains a given property in the scope stack.
|
|
|
|
pub fn find(
|
|
|
|
&self,
|
|
|
|
name: &Multiname,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<Option<Object<'gc>>, Error> {
|
|
|
|
if let Some(qname) = self.locals().resolve_multiname(name) {
|
|
|
|
if self.locals().has_property(&qname) {
|
|
|
|
return Ok(Some(*self.locals()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(scope) = self.parent() {
|
2020-02-11 04:58:15 +00:00
|
|
|
return scope.find(name, avm, context);
|
2020-02-11 04:28:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: This should actually be an error.
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
2020-02-10 19:54:55 +00:00
|
|
|
/// Resolve a particular value in the scope chain.
|
|
|
|
pub fn resolve(
|
|
|
|
&self,
|
2020-02-11 04:28:46 +00:00
|
|
|
name: &Multiname,
|
2020-02-10 19:54:55 +00:00
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-11 04:28:46 +00:00
|
|
|
if let Some(qname) = self.locals().resolve_multiname(name) {
|
|
|
|
if self.locals().has_property(&qname) {
|
|
|
|
return self.locals().get_property(&qname, avm, context);
|
|
|
|
}
|
2020-02-10 19:54:55 +00:00
|
|
|
}
|
2020-02-11 04:28:46 +00:00
|
|
|
|
2020-02-10 19:54:55 +00:00
|
|
|
if let Some(scope) = self.parent() {
|
2020-02-11 04:58:15 +00:00
|
|
|
return scope.resolve(name, avm, context);
|
2020-02-10 19:54:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: Should undefined variables halt execution?
|
|
|
|
Ok(Value::Undefined.into())
|
|
|
|
}
|
|
|
|
}
|