use crate::avm1::object::Object; use crate::avm1::{ActionContext, Error}; use gc_arena::GcCell; #[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 PartialEq for Value<'_> { fn eq(&self, other: &Self) -> bool { match self { Value::Undefined => match other { Value::Undefined => true, _ => false, }, Value::Null => match other { Value::Null => true, _ => false, }, Value::Bool(value) => match other { Value::Bool(other_value) => value == other_value, _ => false, }, Value::Number(value) => match other { Value::Number(other_value) => { (value == other_value) || (value.is_nan() && other_value.is_nan()) } _ => false, }, Value::String(value) => match other { Value::String(other_value) => value == other_value, _ => false, }, Value::Object(value) => match other { Value::Object(other_value) => value.as_ptr() == other_value.as_ptr(), _ => false, }, } } } 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 as_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.read().as_string(), } } 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>, Error> { if let Value::Object(object) = self { Ok(object.to_owned()) } else { Err(format!("Expected Object, found {:?}", self).into()) } } pub fn call( &self, context: &mut ActionContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], ) -> Result, Error> { if let Value::Object(object) = self { Ok(object.read().call(context, this, args)) } else { Err(format!("Expected function, found {:?}", self).into()) } } }