Implement closure scope
This commit is contained in:
parent
d757ce0cd2
commit
22f75b7a4c
|
@ -118,9 +118,14 @@ impl<'gc> Avm1<'gc> {
|
|||
}
|
||||
|
||||
/// Add a stack frame that executes code in function scope
|
||||
pub fn insert_stack_frame_for_function(&mut self, avm1func: &function::Avm1Function, this: GcCell<'gc, Object<'gc>>, args: GcCell<'gc, Object<'gc>>, action_context: &mut ActionContext<'_, 'gc, '_>) {
|
||||
let scope = GcCell::allocate(action_context.gc_context, Scope::from_global_object(self.globals));
|
||||
self.stack_frames.push(Activation::from_action(avm1func.swf_version(), avm1func.data(), scope, this, Some(args)));
|
||||
pub fn insert_stack_frame_for_function(&mut self,
|
||||
avm1func: &function::Avm1Function<'gc>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: GcCell<'gc, Object<'gc>>,
|
||||
action_context: &mut ActionContext<'_, 'gc, '_>) {
|
||||
let locals = GcCell::allocate(action_context.gc_context, Object::bare_object());
|
||||
let child_scope = GcCell::allocate(action_context.gc_context, Scope::from_parent_scope_with_object(avm1func.scope(), locals));
|
||||
self.stack_frames.push(Activation::from_action(avm1func.swf_version(), avm1func.data(), child_scope, this, Some(args)));
|
||||
}
|
||||
|
||||
/// Retrieve the current AVM execution frame.
|
||||
|
@ -590,7 +595,8 @@ impl<'gc> Avm1<'gc> {
|
|||
) -> Result<(), Error> {
|
||||
let swf_version = self.current_stack_frame().unwrap().swf_version();
|
||||
let func_data = self.current_stack_frame().unwrap().data().to_subslice(actions).unwrap();
|
||||
let func = Value::Object(GcCell::allocate(context.gc_context, Object::action_function(swf_version, func_data, name, params)));
|
||||
let scope = self.current_stack_frame().unwrap().scope_cell();
|
||||
let func = Value::Object(GcCell::allocate(context.gc_context, Object::action_function(swf_version, func_data, name, params, scope)));
|
||||
|
||||
if name == "" {
|
||||
self.push(func);
|
||||
|
@ -1244,14 +1250,18 @@ impl<'gc> Avm1<'gc> {
|
|||
var_path: &str,
|
||||
value: Value<'gc>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some((node, var_name)) =
|
||||
Self::resolve_slash_path_variable(context.target_clip, context.root, var_path)
|
||||
{
|
||||
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
|
||||
let object = clip.object().as_object()?;
|
||||
object
|
||||
.write(context.gc_context)
|
||||
.set(var_name, value, self, context, object);
|
||||
let this = self.current_stack_frame().unwrap().this_cell();
|
||||
let unstored_value = self.current_stack_frame().unwrap().scope().overwrite(var_path, value, context.gc_context);
|
||||
if let Some(value) = unstored_value {
|
||||
if let Some((node, var_name)) =
|
||||
Self::resolve_slash_path_variable(context.target_clip, context.root, var_path)
|
||||
{
|
||||
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
|
||||
clip.object()
|
||||
.as_object()?
|
||||
.write(context.gc_context)
|
||||
.set(var_name, value, self, context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -90,6 +90,11 @@ impl<'gc> Activation<'gc> {
|
|||
self.scope.write(mc)
|
||||
}
|
||||
|
||||
/// Returns AVM local variable scope for reference.
|
||||
pub fn scope_cell(&self) -> GcCell<'gc, Scope<'gc>> {
|
||||
self.scope.clone()
|
||||
}
|
||||
|
||||
/// Resolve a particular named local variable within this activation.
|
||||
pub fn resolve(&self, name: &str) -> Value<'gc> {
|
||||
if name == "this" {
|
||||
|
@ -120,4 +125,9 @@ impl<'gc> Activation<'gc> {
|
|||
pub fn define(&self, name: &str, value: Value<'gc>, mc: MutationContext<'gc, '_>) {
|
||||
self.scope().define(name, value, mc)
|
||||
}
|
||||
|
||||
/// Returns value of `this` as a reference.
|
||||
pub fn this_cell(&self) -> GcCell<'gc, Object<'gc>> {
|
||||
self.this
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ use crate::tag_utils::SwfSlice;
|
|||
use crate::avm1::{Avm1, ActionContext};
|
||||
use crate::avm1::object::Object;
|
||||
use crate::avm1::value::Value;
|
||||
use crate::avm1::scope::Scope;
|
||||
|
||||
pub type NativeFunction<'gc> = fn(
|
||||
&mut Avm1<'gc>,
|
||||
|
@ -15,7 +16,7 @@ pub type NativeFunction<'gc> = fn(
|
|||
|
||||
/// Represents a function defined in the AVM1 runtime.
|
||||
#[derive(Clone)]
|
||||
pub struct Avm1Function {
|
||||
pub struct Avm1Function<'gc> {
|
||||
/// The file format version of the SWF that generated this function.
|
||||
swf_version: u8,
|
||||
|
||||
|
@ -27,10 +28,13 @@ pub struct Avm1Function {
|
|||
|
||||
/// The names of the function parameters.
|
||||
params: Vec<String>,
|
||||
|
||||
/// The scope the function was born into.
|
||||
scope: GcCell<'gc, Scope<'gc>>
|
||||
}
|
||||
|
||||
impl Avm1Function {
|
||||
pub fn new(swf_version: u8, actions: SwfSlice, name: &str, params: &[&str]) -> Avm1Function {
|
||||
impl<'gc> Avm1Function<'gc> {
|
||||
pub fn new(swf_version: u8, actions: SwfSlice, name: &str, params: &[&str], scope: GcCell<'gc, Scope<'gc>>) -> Self {
|
||||
let name = match name {
|
||||
"" => None,
|
||||
name => Some(name.to_string())
|
||||
|
@ -40,7 +44,8 @@ impl Avm1Function {
|
|||
swf_version: swf_version,
|
||||
data: actions,
|
||||
name: name,
|
||||
params: params.into_iter().map(|s| s.to_string()).collect()
|
||||
params: params.into_iter().map(|s| s.to_string()).collect(),
|
||||
scope: scope
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,6 +56,16 @@ impl Avm1Function {
|
|||
pub fn data(&self) -> SwfSlice {
|
||||
self.data.clone()
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> GcCell<'gc, Scope<'gc>> {
|
||||
self.scope.clone()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for Avm1Function<'gc> {
|
||||
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||
self.scope.trace(cc);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a function that can be defined in the Ruffle runtime or by the
|
||||
|
@ -61,7 +76,7 @@ pub enum Executable<'gc> {
|
|||
Native(NativeFunction<'gc>),
|
||||
|
||||
/// ActionScript data defined by a previous action.
|
||||
Action(Avm1Function)
|
||||
Action(Avm1Function<'gc>)
|
||||
}
|
||||
|
||||
impl<'gc> Executable<'gc> {
|
||||
|
@ -78,10 +93,10 @@ impl<'gc> Executable<'gc> {
|
|||
let mut arguments = Object::object(ac.gc_context);
|
||||
|
||||
for i in 0..args.len() {
|
||||
arguments.set(&format!("{}", i), args.get(i).unwrap().clone())
|
||||
arguments.force_set(&format!("{}", i), args.get(i).unwrap().clone());
|
||||
}
|
||||
|
||||
arguments.set("length", Value::Number(args.len() as f64));
|
||||
arguments.force_set("length", Value::Number(args.len() as f64));
|
||||
|
||||
avm.insert_stack_frame_for_function(af, this, GcCell::allocate(ac.gc_context, arguments), ac);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::avm1::{ActionContext, Avm1, Value};
|
||||
use crate::avm1::function::{Executable, NativeFunction, Avm1Function};
|
||||
use crate::avm1::scope::Scope;
|
||||
use crate::display_object::DisplayNode;
|
||||
use crate::tag_utils::SwfSlice;
|
||||
use core::fmt;
|
||||
|
@ -156,10 +157,10 @@ impl<'gc> Object<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn action_function(swf_version: u8, actions: SwfSlice, name: &str, params: &[&str]) -> Self {
|
||||
pub fn action_function(swf_version: u8, actions: SwfSlice, name: &str, params: &[&str], scope: GcCell<'gc, Scope<'gc>>) -> Self {
|
||||
Self {
|
||||
type_of: TYPE_OF_FUNCTION,
|
||||
function: Some(Executable::Action(Avm1Function::new(swf_version, actions, name, params))),
|
||||
function: Some(Executable::Action(Avm1Function::new(swf_version, actions, name, params, scope))),
|
||||
display_node: None,
|
||||
values: HashMap::new()
|
||||
}
|
||||
|
@ -251,6 +252,15 @@ impl<'gc> Object<'gc> {
|
|||
Value::Undefined
|
||||
}
|
||||
|
||||
/// Retrieve a value from an object if and only if the value in the object
|
||||
/// property is non-virtual.
|
||||
pub fn force_get(&self, name: &str) -> Value<'gc> {
|
||||
if let Some(Property::Stored { value, .. }) = self.values.get(name) {
|
||||
return value.to_owned();
|
||||
}
|
||||
Value::Undefined
|
||||
}
|
||||
|
||||
pub fn has_property(&self, name: &str) -> bool {
|
||||
self.values.contains_key(name)
|
||||
}
|
||||
|
@ -260,7 +270,10 @@ impl<'gc> Object<'gc> {
|
|||
}
|
||||
|
||||
pub fn iter_values(&self) -> impl Iterator<Item=(&String, &Value<'gc>)> {
|
||||
self.values.iter()
|
||||
self.values.iter().filter_map(|(k, p)| match p {
|
||||
Property::Virtual {..} => None,
|
||||
Property::Stored { value, .. } => Some((k, value))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Represents AVM1 scope chain resolution.
|
||||
|
||||
use std::cell::Ref;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use crate::avm1::{Object, Value};
|
||||
|
||||
|
@ -51,6 +51,11 @@ impl<'gc> Scope<'gc> {
|
|||
self.values.read()
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
@ -62,7 +67,7 @@ impl<'gc> Scope<'gc> {
|
|||
/// Resolve a particular value in the scope chain.
|
||||
pub fn resolve(&self, name: &str) -> Value<'gc> {
|
||||
if self.locals().has_property(name) {
|
||||
return self.locals().get(name);
|
||||
return self.locals().force_get(name);
|
||||
}
|
||||
|
||||
if let Some(scope) = self.parent() {
|
||||
|
@ -85,8 +90,29 @@ impl<'gc> Scope<'gc> {
|
|||
false
|
||||
}
|
||||
|
||||
/// Set a particular value in the current scope (and all child scopes)
|
||||
/// Update a particular value in the scope chain, but only if it was
|
||||
/// previously defined.
|
||||
///
|
||||
/// 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. If no value can be
|
||||
/// overwritten, then we return the unwritten value so that it may be used
|
||||
/// in some other way.
|
||||
pub fn overwrite(&self, name: &str, value: Value<'gc>, mc: MutationContext<'gc, '_>) -> Option<Value<'gc>> {
|
||||
if self.locals().has_property(name) {
|
||||
self.locals_mut(mc).force_set(name, value);
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(scope) = self.parent() {
|
||||
return scope.overwrite(name, value, mc);
|
||||
}
|
||||
|
||||
Some(value)
|
||||
}
|
||||
|
||||
/// Set a particular value in the locals for this scope.
|
||||
pub fn define(&self, name: &str, value: Value<'gc>, mc: MutationContext<'gc, '_>) {
|
||||
self.values.write(mc).set(name, value);
|
||||
self.locals_mut(mc).force_set(name, value);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue