diff --git a/core/src/avm1.rs b/core/src/avm1.rs index ed4d35711..39a1444a3 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -2,7 +2,7 @@ use crate::avm1::builtins::register_builtins; use crate::avm1::object::Object; use crate::prelude::*; -use gc_arena::{GcCell, MutationContext}; +use gc_arena::MutationContext; use rand::{rngs::SmallRng, Rng, SeedableRng}; use std::collections::HashMap; @@ -12,6 +12,9 @@ use swf::avm1::read::Reader; mod builtins; pub mod movie_clip; pub mod object; +mod value; + +pub use value::Value; pub struct ActionContext<'a, 'gc, 'gc_context> { pub gc_context: gc_arena::MutationContext<'gc, 'gc_context>, @@ -1222,142 +1225,3 @@ impl<'gc> Avm1<'gc> { Err("Unimplemented action: With".into()) } } - -#[derive(Clone, Debug)] -#[allow(dead_code)] -pub enum Value<'gc> { - Undefined, - Null, - Bool(bool), - Number(f64), - String(String), - Object(GcCell<'gc, Object<'gc>>), -} - -unsafe impl<'gc> gc_arena::Collect for Value<'gc> { - fn trace(&self, cc: gc_arena::CollectionContext) { - if let Value::Object(object) = self { - object.trace(cc); - } - } -} - -impl<'gc> Value<'gc> { - fn into_number_v1(self) -> f64 { - match self { - Value::Bool(true) => 1.0, - Value::Number(v) => v, - Value::String(v) => v.parse().unwrap_or(0.0), - _ => 0.0, - } - } - - fn into_number(self) -> f64 { - // ECMA-262 2nd edtion s. 9.3 ToNumber - use std::f64::NAN; - match self { - Value::Undefined => NAN, - Value::Null => NAN, - Value::Bool(false) => 0.0, - Value::Bool(true) => 1.0, - Value::Number(v) => v, - Value::String(v) => v.parse().unwrap_or(NAN), // TODO(Herschel): Handle Infinity/etc.? - Value::Object(_object) => { - log::error!("Unimplemented: Object ToNumber"); - 0.0 - } - } - } - - fn from_bool_v1(value: bool, swf_version: u8) -> Value<'gc> { - // SWF version 4 did not have true bools and will push bools as 0 or 1. - // e.g. SWF19 p. 72: - // "If the numbers are equal, true is pushed to the stack for SWF 5 and later. For SWF 4, 1 is pushed to the stack." - if swf_version >= 5 { - Value::Bool(value) - } else { - Value::Number(if value { 1.0 } else { 0.0 }) - } - } - - fn into_string(self) -> String { - match self { - Value::Undefined => "undefined".to_string(), - Value::Null => "null".to_string(), - Value::Bool(v) => v.to_string(), - Value::Number(v) => v.to_string(), // TODO(Herschel): Rounding for int? - Value::String(v) => v, - Value::Object(_) => "[Object object]".to_string(), // TODO: object.toString() - } - } - - fn as_bool(&self) -> bool { - match *self { - Value::Bool(v) => v, - Value::Number(v) => v != 0.0, - // TODO(Herschel): Value::String(v) => ?? - _ => false, - } - } - - fn type_of(&self) -> Value<'gc> { - Value::String( - match self { - Value::Undefined => "undefined", - Value::Null => "null", - Value::Number(_) => "number", - Value::Bool(_) => "boolean", - Value::String(_) => "string", - Value::Object(object) => object.read().type_of(), - } - .to_string(), - ) - } - - fn as_i32(&self) -> Result { - self.as_f64().map(|n| n as i32) - } - - fn as_u32(&self) -> Result { - self.as_f64().map(|n| n as u32) - } - - fn as_i64(&self) -> Result { - self.as_f64().map(|n| n as i64) - } - - pub fn as_f64(&self) -> Result { - match *self { - Value::Number(v) => Ok(v), - _ => Err(format!("Expected Number, found {:?}", self).into()), - } - } - - fn as_string(&self) -> Result<&String, Error> { - match self { - Value::String(s) => Ok(s), - _ => Err(format!("Expected String, found {:?}", self).into()), - } - } - - fn as_object(&self) -> Result<&GcCell<'gc, Object<'gc>>, Error> { - if let Value::Object(object) = self { - Ok(object) - } else { - Err(format!("Expected Object, found {:?}", self).into()) - } - } - - fn call( - &self, - gc_context: MutationContext<'gc, '_>, - this: GcCell<'gc, Object<'gc>>, - args: &[Value<'gc>], - ) -> Result, Error> { - if let Value::Object(object) = self { - Ok(object.read().call(gc_context, this, args)) - } else { - Err(format!("Expected function, found {:?}", self).into()) - } - } -} diff --git a/core/src/avm1/value.rs b/core/src/avm1/value.rs new file mode 100644 index 000000000..dadf0c43c --- /dev/null +++ b/core/src/avm1/value.rs @@ -0,0 +1,142 @@ +use crate::avm1::object::Object; +use crate::avm1::Error; +use gc_arena::{GcCell, MutationContext}; + +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub enum Value<'gc> { + Undefined, + Null, + Bool(bool), + Number(f64), + String(String), + Object(GcCell<'gc, Object<'gc>>), +} + +unsafe impl<'gc> gc_arena::Collect for Value<'gc> { + fn trace(&self, cc: gc_arena::CollectionContext) { + if let Value::Object(object) = self { + object.trace(cc); + } + } +} + +impl<'gc> Value<'gc> { + pub fn into_number_v1(self) -> f64 { + match self { + Value::Bool(true) => 1.0, + Value::Number(v) => v, + Value::String(v) => v.parse().unwrap_or(0.0), + _ => 0.0, + } + } + + pub fn into_number(self) -> f64 { + // ECMA-262 2nd edtion s. 9.3 ToNumber + use std::f64::NAN; + match self { + Value::Undefined => NAN, + Value::Null => NAN, + Value::Bool(false) => 0.0, + Value::Bool(true) => 1.0, + Value::Number(v) => v, + Value::String(v) => v.parse().unwrap_or(NAN), // TODO(Herschel): Handle Infinity/etc.? + Value::Object(_object) => { + log::error!("Unimplemented: Object ToNumber"); + 0.0 + } + } + } + + pub fn from_bool_v1(value: bool, swf_version: u8) -> Value<'gc> { + // SWF version 4 did not have true bools and will push bools as 0 or 1. + // e.g. SWF19 p. 72: + // "If the numbers are equal, true is pushed to the stack for SWF 5 and later. For SWF 4, 1 is pushed to the stack." + if swf_version >= 5 { + Value::Bool(value) + } else { + Value::Number(if value { 1.0 } else { 0.0 }) + } + } + + pub fn into_string(self) -> String { + match self { + Value::Undefined => "undefined".to_string(), + Value::Null => "null".to_string(), + Value::Bool(v) => v.to_string(), + Value::Number(v) => v.to_string(), // TODO(Herschel): Rounding for int? + Value::String(v) => v, + Value::Object(_) => "[Object object]".to_string(), // TODO: object.toString() + } + } + + pub fn as_bool(&self) -> bool { + match *self { + Value::Bool(v) => v, + Value::Number(v) => v != 0.0, + // TODO(Herschel): Value::String(v) => ?? + _ => false, + } + } + + pub fn type_of(&self) -> Value<'gc> { + Value::String( + match self { + Value::Undefined => "undefined", + Value::Null => "null", + Value::Number(_) => "number", + Value::Bool(_) => "boolean", + Value::String(_) => "string", + Value::Object(object) => object.read().type_of(), + } + .to_string(), + ) + } + + pub fn as_i32(&self) -> Result { + self.as_f64().map(|n| n as i32) + } + + pub fn as_u32(&self) -> Result { + self.as_f64().map(|n| n as u32) + } + + pub fn as_i64(&self) -> Result { + self.as_f64().map(|n| n as i64) + } + + pub fn as_f64(&self) -> Result { + match *self { + Value::Number(v) => Ok(v), + _ => Err(format!("Expected Number, found {:?}", self).into()), + } + } + + pub fn as_string(&self) -> Result<&String, Error> { + match self { + Value::String(s) => Ok(s), + _ => Err(format!("Expected String, found {:?}", self).into()), + } + } + + pub fn as_object(&self) -> Result<&GcCell<'gc, Object<'gc>>, Error> { + if let Value::Object(object) = self { + Ok(object) + } else { + Err(format!("Expected Object, found {:?}", self).into()) + } + } + + pub fn call( + &self, + gc_context: MutationContext<'gc, '_>, + this: GcCell<'gc, Object<'gc>>, + args: &[Value<'gc>], + ) -> Result, Error> { + if let Value::Object(object) = self { + Ok(object.read().call(gc_context, this, args)) + } else { + Err(format!("Expected function, found {:?}", self).into()) + } + } +}