//! Represents AVM2 scope chain resolution. use crate::avm2::activation::Activation; use crate::avm2::names::Multiname; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; 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>>, 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>>, 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>>, with_object: Object<'gc>, mc: MutationContext<'gc, '_>, ) -> GcCell<'gc, Self> { GcCell::allocate( mc, Scope { parent: scope_stack, class: ScopeClass::With, values: with_object, }, ) } pub fn pop_scope(&self) -> Option>> { self.parent } /// Returns a reference to the current local scope object. pub fn locals(&self) -> &Object<'gc> { &self.values } /// Returns a reference to the current global scope object. /// /// By convention, the global scope is at the bottom of the scope stack. pub fn globals(&self) -> Object<'gc> { if let Some(parent) = self.parent { parent.read().globals() } else { self.values } } /// Returns a reference to the current local scope object for mutation. 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>> { match self.parent { Some(ref p) => Some(p.read()), None => None, } } pub fn parent_cell(&self) -> Option>> { self.parent } /// Find an object that contains a given property in the scope stack. /// /// This function yields `None` if no such scope exists. pub fn find( &self, name: &Multiname<'gc>, activation: &mut Activation<'_, 'gc, '_>, ) -> Result>, 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() { return scope.find(name, activation); } if let Some(domain) = self.locals().as_application_domain() { let script = domain.get_defining_script(name)?; if let Some((_qname, mut script)) = script { return Ok(Some(script.globals(&mut activation.context)?)); } } Ok(None) } /// Resolve a particular value in the scope chain. /// /// This function yields `None` if no such scope exists to provide the /// property's value. pub fn resolve( &mut self, name: &Multiname<'gc>, activation: &mut Activation<'_, 'gc, '_>, ) -> Result>, Error> { if let Some(qname) = self.locals().resolve_multiname(name)? { if self.locals().has_property(&qname)? { return Ok(Some(self.values.get_property( self.values, &qname, activation, )?)); } } if let Some(parent) = self.parent { return parent .write(activation.context.gc_context) .resolve(name, activation); } if let Some(domain) = self.locals().as_application_domain() { let script = domain.get_defining_script(name)?; if let Some((qname, mut script)) = script { let mut script_scope = script.globals(&mut activation.context)?; return Ok(Some(script_scope.get_property( script_scope, &qname, activation, )?)); } } //TODO: Should undefined variables halt execution? Ok(None) } }