Implement closure scope

This commit is contained in:
David Wendt 2019-09-21 21:41:30 -04:00 committed by Mike Welsh
parent d757ce0cd2
commit 22f75b7a4c
5 changed files with 100 additions and 26 deletions

View File

@ -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(())

View File

@ -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
}
}

View File

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

View File

@ -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(

View File

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