avm1: Merge #97, impl From<*> for Value

Implement From<*> for Value for better dev ergonomics
This commit is contained in:
Mike Welsh 2019-10-21 11:43:29 -07:00 committed by GitHub
commit e02204a78f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 293 additions and 271 deletions

View File

@ -546,7 +546,7 @@ impl<'gc> Avm1<'gc> {
fn action_add(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_add(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
self.push(Value::Number(b.into_number_v1() + a.into_number_v1())); self.push(b.into_number_v1() + a.into_number_v1());
Ok(()) Ok(())
} }
@ -559,12 +559,12 @@ impl<'gc> Avm1<'gc> {
if let Value::String(a) = a { if let Value::String(a) = a {
let mut s = b.into_string(); let mut s = b.into_string();
s.push_str(&a); s.push_str(&a);
self.push(Value::String(s)); self.push(s);
} else if let Value::String(mut b) = b { } else if let Value::String(mut b) = b {
b.push_str(&a.into_string()); b.push_str(&a.into_string());
self.push(Value::String(b)); self.push(b);
} else { } else {
self.push(Value::Number(b.as_number() + a.as_number())); self.push(b.as_number() + a.as_number());
} }
Ok(()) Ok(())
} }
@ -581,7 +581,7 @@ impl<'gc> Avm1<'gc> {
fn action_ascii_to_char(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_ascii_to_char(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
// TODO(Herschel): Results on incorrect operands? // TODO(Herschel): Results on incorrect operands?
let val = (self.pop()?.as_f64()? as u8) as char; let val = (self.pop()?.as_f64()? as u8) as char;
self.push(Value::String(val.to_string())); self.push(val.to_string());
Ok(()) Ok(())
} }
@ -589,7 +589,7 @@ impl<'gc> Avm1<'gc> {
// TODO(Herschel): Results on incorrect operands? // TODO(Herschel): Results on incorrect operands?
let s = self.pop()?.into_string(); let s = self.pop()?.into_string();
let result = s.bytes().nth(0).unwrap_or(0); let result = s.bytes().nth(0).unwrap_or(0);
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -606,7 +606,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?.as_u32()?; let a = self.pop()?.as_u32()?;
let b = self.pop()?.as_u32()?; let b = self.pop()?.as_u32()?;
let result = a & b; let result = a & b;
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -614,7 +614,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?.as_i32()? & 0b11111; // Only 5 bits used for shift count let a = self.pop()?.as_i32()? & 0b11111; // Only 5 bits used for shift count
let b = self.pop()?.as_i32()?; let b = self.pop()?.as_i32()?;
let result = b << a; let result = b << a;
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -622,7 +622,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?.as_u32()?; let a = self.pop()?.as_u32()?;
let b = self.pop()?.as_u32()?; let b = self.pop()?.as_u32()?;
let result = a | b; let result = a | b;
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -630,7 +630,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?.as_i32()? & 0b11111; // Only 5 bits used for shift count let a = self.pop()?.as_i32()? & 0b11111; // Only 5 bits used for shift count
let b = self.pop()?.as_i32()?; let b = self.pop()?.as_i32()?;
let result = b >> a; let result = b >> a;
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -638,7 +638,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?.as_u32()? & 0b11111; // Only 5 bits used for shift count let a = self.pop()?.as_u32()? & 0b11111; // Only 5 bits used for shift count
let b = self.pop()?.as_u32()?; let b = self.pop()?.as_u32()?;
let result = b >> a; let result = b >> a;
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -646,7 +646,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?.as_u32()?; let a = self.pop()?.as_u32()?;
let b = self.pop()?.as_u32()?; let b = self.pop()?.as_u32()?;
let result = b ^ a; let result = b ^ a;
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -750,7 +750,7 @@ impl<'gc> Avm1<'gc> {
fn action_decrement(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_decrement(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let a = self.pop()?.as_number(); let a = self.pop()?.as_number();
self.push(Value::Number(a - 1.0)); self.push(a - 1.0);
Ok(()) Ok(())
} }
@ -774,10 +774,7 @@ impl<'gc> Avm1<'gc> {
context.gc_context, context.gc_context,
); );
let func = Avm1Function::from_df1(swf_version, func_data, name, params, scope); let func = Avm1Function::from_df1(swf_version, func_data, name, params, scope);
let func_obj = Value::Object(GcCell::allocate( let func_obj = GcCell::allocate(context.gc_context, Object::action_function(func));
context.gc_context,
Object::action_function(func),
));
if name == "" { if name == "" {
self.push(func_obj); self.push(func_obj);
} else { } else {
@ -808,10 +805,7 @@ impl<'gc> Avm1<'gc> {
context.gc_context, context.gc_context,
); );
let func = Avm1Function::from_df2(swf_version, func_data, action_func, scope); let func = Avm1Function::from_df2(swf_version, func_data, action_func, scope);
let func_obj = Value::Object(GcCell::allocate( let func_obj = GcCell::allocate(context.gc_context, Object::action_function(func));
context.gc_context,
Object::action_function(func),
));
if action_func.name == "" { if action_func.name == "" {
self.push(func_obj); self.push(func_obj);
} else { } else {
@ -858,7 +852,7 @@ impl<'gc> Avm1<'gc> {
let object = self.pop()?.as_object()?; let object = self.pop()?.as_object()?;
let success = object.write(context.gc_context).delete(name); let success = object.write(context.gc_context).delete(name);
self.push(Value::Bool(success)); self.push(success);
Ok(()) Ok(())
} }
@ -869,7 +863,7 @@ impl<'gc> Avm1<'gc> {
//Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns //Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns
//a boolean based on if the delete actually deleted something. //a boolean based on if the delete actually deleted something.
let did_exist = Value::Bool(self.current_stack_frame().unwrap().read().is_defined(name)); let did_exist = self.current_stack_frame().unwrap().read().is_defined(name);
self.current_stack_frame() self.current_stack_frame()
.unwrap() .unwrap()
@ -890,7 +884,7 @@ impl<'gc> Avm1<'gc> {
// In SWF 4, the result is the string #ERROR#."" // In SWF 4, the result is the string #ERROR#.""
// Seems to be untrue for SWF v4, I get 1.#INF. // Seems to be untrue for SWF v4, I get 1.#INF.
self.push(Value::Number(b.into_number_v1() / a.into_number_v1())); self.push(b.into_number_v1() / a.into_number_v1());
Ok(()) Ok(())
} }
@ -919,7 +913,7 @@ impl<'gc> Avm1<'gc> {
}; };
for k in ob.read().get_keys() { for k in ob.read().get_keys() {
self.push(Value::String(k)); self.push(k);
} }
Ok(()) Ok(())
@ -954,7 +948,7 @@ impl<'gc> Avm1<'gc> {
(Value::Number(a), Value::String(b)) => a == b.parse().unwrap_or(std::f64::NAN), (Value::Number(a), Value::String(b)) => a == b.parse().unwrap_or(std::f64::NAN),
_ => false, _ => false,
}; };
self.push(Value::Bool(result)); self.push(result);
Ok(()) Ok(())
} }
@ -1017,7 +1011,7 @@ impl<'gc> Avm1<'gc> {
} }
fn action_get_time(&mut self, context: &mut ActionContext) -> Result<(), Error> { fn action_get_time(&mut self, context: &mut ActionContext) -> Result<(), Error> {
self.push(Value::Number(context.global_time as f64)); self.push(context.global_time as f64);
Ok(()) Ok(())
} }
@ -1217,7 +1211,7 @@ impl<'gc> Avm1<'gc> {
fn action_increment(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_increment(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let a = self.pop()?.as_number(); let a = self.pop()?.as_number();
self.push(Value::Number(a + 1.0)); self.push(a + 1.0);
Ok(()) Ok(())
} }
@ -1272,7 +1266,7 @@ impl<'gc> Avm1<'gc> {
(a, b) => b.as_number() < a.as_number(), (a, b) => b.as_number() < a.as_number(),
}; };
self.push(Value::Bool(result)); self.push(result);
Ok(()) Ok(())
} }
@ -1289,7 +1283,7 @@ impl<'gc> Avm1<'gc> {
// TODO(Herschel): Results on incorrect operands? // TODO(Herschel): Results on incorrect operands?
use std::convert::TryFrom; use std::convert::TryFrom;
let val = char::try_from(self.pop()?.as_f64()? as u32)?; let val = char::try_from(self.pop()?.as_f64()? as u32)?;
self.push(Value::String(val.to_string())); self.push(val.to_string());
Ok(()) Ok(())
} }
@ -1297,7 +1291,7 @@ impl<'gc> Avm1<'gc> {
// TODO(Herschel): Results on incorrect operands? // TODO(Herschel): Results on incorrect operands?
let s = self.pop()?.into_string(); let s = self.pop()?.into_string();
let result = s.chars().nth(0).unwrap_or('\0') as u32; let result = s.chars().nth(0).unwrap_or('\0') as u32;
self.push(Value::Number(result.into())); self.push(result);
Ok(()) Ok(())
} }
@ -1307,14 +1301,14 @@ impl<'gc> Avm1<'gc> {
let start = self.pop()?.as_f64()? as usize; let start = self.pop()?.as_f64()? as usize;
let s = self.pop()?.into_string(); let s = self.pop()?.into_string();
let result = s[len..len + start].to_string(); // TODO(Herschel): Flash uses UTF-16 internally. let result = s[len..len + start].to_string(); // TODO(Herschel): Flash uses UTF-16 internally.
self.push(Value::String(result)); self.push(result);
Ok(()) Ok(())
} }
fn action_mb_string_length(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_mb_string_length(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
// TODO(Herschel): Result with non-string operands? // TODO(Herschel): Result with non-string operands?
let val = self.pop()?.into_string().len(); let val = self.pop()?.into_string().len();
self.push(Value::Number(val as f64)); self.push(val as f64);
Ok(()) Ok(())
} }
@ -1322,7 +1316,7 @@ impl<'gc> Avm1<'gc> {
// AS1 multiply // AS1 multiply
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
self.push(Value::Number(a.into_number_v1() * b.into_number_v1())); self.push(a.into_number_v1() * b.into_number_v1());
Ok(()) Ok(())
} }
@ -1330,7 +1324,7 @@ impl<'gc> Avm1<'gc> {
// TODO: Wrong operands? // TODO: Wrong operands?
let a = self.pop()?.as_f64()?; let a = self.pop()?.as_f64()?;
let b = self.pop()?.as_f64()?; let b = self.pop()?.as_f64()?;
self.push(Value::Number(a % b)); self.push(a % b);
Ok(()) Ok(())
} }
@ -1432,11 +1426,11 @@ impl<'gc> Avm1<'gc> {
SwfValue::Int(v) => Value::Number(f64::from(*v)), SwfValue::Int(v) => Value::Number(f64::from(*v)),
SwfValue::Float(v) => Value::Number(f64::from(*v)), SwfValue::Float(v) => Value::Number(f64::from(*v)),
SwfValue::Double(v) => Value::Number(*v), SwfValue::Double(v) => Value::Number(*v),
SwfValue::Str(v) => Value::String(v.to_string()), SwfValue::Str(v) => v.to_string().into(),
SwfValue::Register(v) => self.current_register(*v), SwfValue::Register(v) => self.current_register(*v),
SwfValue::ConstantPool(i) => { SwfValue::ConstantPool(i) => {
if let Some(value) = self.constant_pool.get(*i as usize) { if let Some(value) = self.constant_pool.get(*i as usize) {
Value::String(value.to_string()) value.to_string().into()
} else { } else {
log::warn!( log::warn!(
"ActionPush: Constant pool index {} out of range (len = {})", "ActionPush: Constant pool index {} out of range (len = {})",
@ -1463,7 +1457,7 @@ impl<'gc> Avm1<'gc> {
// and the max value gets converted into an i32, so any number > 2^31 - 1 will return 0. // and the max value gets converted into an i32, so any number > 2^31 - 1 will return 0.
let max = self.pop()?.into_number_v1() as i32; let max = self.pop()?.into_number_v1() as i32;
let val = context.rng.gen_range(0, max); let val = context.rng.gen_range(0, max);
self.push(Value::Number(val.into())); self.push(val);
Ok(()) Ok(())
} }
@ -1542,7 +1536,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
let result = a == b; let result = a == b;
self.push(Value::Bool(result)); self.push(result);
Ok(()) Ok(())
} }
@ -1593,13 +1587,13 @@ impl<'gc> Avm1<'gc> {
if target.is_empty() { if target.is_empty() {
context.active_clip = context.start_clip; context.active_clip = context.start_clip;
context.target_clip = Some(context.start_clip); context.target_clip = Some(context.start_clip);
context.target_path = Value::String(target.to_string()); context.target_path = target.into();
} else if let Some(clip) = } else if let Some(clip) =
Avm1::resolve_slash_path(context.start_clip, context.root, target) Avm1::resolve_slash_path(context.start_clip, context.root, target)
{ {
context.target_clip = Some(clip); context.target_clip = Some(clip);
context.active_clip = clip; context.active_clip = clip;
context.target_path = Value::String(target.to_string()); context.target_path = target.into();
} else { } else {
log::warn!("SetTarget failed: {} not found", target); log::warn!("SetTarget failed: {} not found", target);
// TODO: Emulate AVM1 trace error message. // TODO: Emulate AVM1 trace error message.
@ -1701,7 +1695,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?.into_string(); let a = self.pop()?.into_string();
let mut b = self.pop()?.into_string(); let mut b = self.pop()?.into_string();
b.push_str(&a); b.push_str(&a);
self.push(Value::String(b)); self.push(b);
Ok(()) Ok(())
} }
@ -1731,7 +1725,7 @@ impl<'gc> Avm1<'gc> {
.take(len) .take(len)
.map(|c| c as char) .map(|c| c as char)
.collect::<String>(); .collect::<String>();
self.push(Value::String(result)); self.push(result);
Ok(()) Ok(())
} }
@ -1753,7 +1747,7 @@ impl<'gc> Avm1<'gc> {
// Only returns byte length. // Only returns byte length.
// TODO(Herschel): Result with non-string operands? // TODO(Herschel): Result with non-string operands?
let val = self.pop()?.into_string().bytes().len() as f64; let val = self.pop()?.into_string().bytes().len() as f64;
self.push(Value::Number(val)); self.push(val);
Ok(()) Ok(())
} }
@ -1773,7 +1767,7 @@ impl<'gc> Avm1<'gc> {
fn action_subtract(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_subtract(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
self.push(Value::Number(b.into_number_v1() - a.into_number_v1())); self.push(b.into_number_v1() - a.into_number_v1());
Ok(()) Ok(())
} }
@ -1794,19 +1788,19 @@ impl<'gc> Avm1<'gc> {
fn action_to_integer(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_to_integer(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let val = self.pop()?; let val = self.pop()?;
self.push(Value::Number(val.into_number_v1().trunc())); self.push(val.into_number_v1().trunc());
Ok(()) Ok(())
} }
fn action_to_number(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_to_number(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let val = self.pop()?; let val = self.pop()?;
self.push(Value::Number(val.as_number())); self.push(val.as_number());
Ok(()) Ok(())
} }
fn action_to_string(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_to_string(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let val = self.pop()?; let val = self.pop()?;
self.push(Value::String(val.into_string())); self.push(val.into_string());
Ok(()) Ok(())
} }

View File

@ -271,7 +271,7 @@ impl<'gc> Activation<'gc> {
} }
/// Define a named local variable within this activation. /// Define a named local variable within this activation.
pub fn define(&self, name: &str, value: Value<'gc>, mc: MutationContext<'gc, '_>) { pub fn define(&self, name: &str, value: impl Into<Value<'gc>>, mc: MutationContext<'gc, '_>) {
self.scope().define(name, value, mc) self.scope().define(name, value, mc)
} }
@ -304,10 +304,15 @@ impl<'gc> Activation<'gc> {
} }
/// Set a local register. /// Set a local register.
pub fn set_local_register(&mut self, id: u8, value: Value<'gc>, mc: MutationContext<'gc, '_>) { pub fn set_local_register(
&mut self,
id: u8,
value: impl Into<Value<'gc>>,
mc: MutationContext<'gc, '_>,
) {
if let Some(ref mut local_registers) = self.local_registers { if let Some(ref mut local_registers) = self.local_registers {
if let Some(r) = local_registers.write(mc).get_mut(id) { if let Some(r) = local_registers.write(mc).get_mut(id) {
*r = value; *r = value.into();
} }
} }
} }

View File

@ -219,14 +219,14 @@ impl<'gc> Executable<'gc> {
if af.preload_this { if af.preload_this {
//TODO: What happens if you specify both suppress and //TODO: What happens if you specify both suppress and
//preload for this? //preload for this?
frame.set_local_register(preload_r, Value::Object(this), ac.gc_context); frame.set_local_register(preload_r, this, ac.gc_context);
preload_r += 1; preload_r += 1;
} }
if af.preload_arguments { if af.preload_arguments {
//TODO: What happens if you specify both suppress and //TODO: What happens if you specify both suppress and
//preload for arguments? //preload for arguments?
frame.set_local_register(preload_r, Value::Object(argcell), ac.gc_context); frame.set_local_register(preload_r, argcell, ac.gc_context);
preload_r += 1; preload_r += 1;
} }

View File

@ -94,11 +94,7 @@ pub fn create_globals<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc>
globals.force_set_function("isNaN", is_nan, gc_context, EnumSet::empty()); globals.force_set_function("isNaN", is_nan, gc_context, EnumSet::empty());
globals.force_set_function("Boolean", boolean, gc_context, EnumSet::empty()); globals.force_set_function("Boolean", boolean, gc_context, EnumSet::empty());
globals.force_set( globals.force_set("Math", math::create(gc_context), EnumSet::empty());
"Math",
Value::Object(math::create(gc_context)),
EnumSet::empty(),
);
globals.force_set_function("getURL", getURL, gc_context, EnumSet::empty()); globals.force_set_function("getURL", getURL, gc_context, EnumSet::empty());
globals.force_set_function("Number", number, gc_context, EnumSet::empty()); globals.force_set_function("Number", number, gc_context, EnumSet::empty());
globals.force_set_function("random", random, gc_context, EnumSet::empty()); globals.force_set_function("random", random, gc_context, EnumSet::empty());
@ -121,13 +117,18 @@ mod tests {
use crate::avm1::Error; use crate::avm1::Error;
macro_rules! test_std { macro_rules! test_std {
( $test: ident, $fun: expr, $version: expr, $($args: expr => $out: expr),* ) => { ( $test: ident, $fun: expr, $version: expr, $([$($arg: expr),*] => $out: expr),* ) => {
#[test] #[test]
fn $test() -> Result<(), Error> { fn $test() -> Result<(), Error> {
with_avm($version, |avm, context, this| { with_avm($version, |avm, context, this| {
$( $(
assert_eq!($fun(avm, context, this, $args), $out); #[allow(unused_mut)]
let mut args: Vec<Value> = Vec::new();
$(
args.push($arg.into());
)*
assert_eq!($fun(avm, context, this, &args), $out.into());
)* )*
Ok(()) Ok(())
@ -137,82 +138,82 @@ mod tests {
} }
test_std!(boolean_function, boolean, 19, test_std!(boolean_function, boolean, 19,
&[Value::Bool(true)] => Value::Bool(true), [true] => true,
&[Value::Bool(false)] => Value::Bool(false), [false] => false,
&[Value::Number(10.0)] => Value::Bool(true), [10.0] => true,
&[Value::Number(-10.0)] => Value::Bool(true), [-10.0] => true,
&[Value::Number(0.0)] => Value::Bool(false), [0.0] => false,
&[Value::Number(std::f64::INFINITY)] => Value::Bool(true), [std::f64::INFINITY] => true,
&[Value::Number(std::f64::NAN)] => Value::Bool(false), [std::f64::NAN] => false,
&[Value::String("".to_string())] => Value::Bool(false), [""] => false,
&[Value::String("Hello".to_string())] => Value::Bool(true), ["Hello"] => true,
&[Value::String(" ".to_string())] => Value::Bool(true), [" "] => true,
&[Value::String("0".to_string())] => Value::Bool(true), ["0"] => true,
&[Value::String("1".to_string())] => Value::Bool(true), ["1"] => true,
&[] => Value::Bool(false) [] => false
); );
test_std!(boolean_function_swf6, boolean, 6, test_std!(boolean_function_swf6, boolean, 6,
&[Value::Bool(true)] => Value::Bool(true), [true] => true,
&[Value::Bool(false)] => Value::Bool(false), [false] => false,
&[Value::Number(10.0)] => Value::Bool(true), [10.0] => true,
&[Value::Number(-10.0)] => Value::Bool(true), [-10.0] => true,
&[Value::Number(0.0)] => Value::Bool(false), [0.0] => false,
&[Value::Number(std::f64::INFINITY)] => Value::Bool(true), [std::f64::INFINITY] => true,
&[Value::Number(std::f64::NAN)] => Value::Bool(false), [std::f64::NAN] => false,
&[Value::String("".to_string())] => Value::Bool(false), [""] => false,
&[Value::String("Hello".to_string())] => Value::Bool(false), ["Hello"] => false,
&[Value::String(" ".to_string())] => Value::Bool(false), [" "] => false,
&[Value::String("0".to_string())] => Value::Bool(false), ["0"] => false,
&[Value::String("1".to_string())] => Value::Bool(true), ["1"] => true,
&[] => Value::Bool(false) [] => false
); );
test_std!(is_nan_function, is_nan, 19, test_std!(is_nan_function, is_nan, 19,
&[Value::Bool(true)] => Value::Bool(false), [true] => false,
&[Value::Bool(false)] => Value::Bool(false), [false] => false,
&[Value::Number(10.0)] => Value::Bool(false), [10.0] => false,
&[Value::Number(-10.0)] => Value::Bool(false), [-10.0] => false,
&[Value::Number(0.0)] => Value::Bool(false), [0.0] => false,
&[Value::Number(std::f64::INFINITY)] => Value::Bool(false), [std::f64::INFINITY] => false,
&[Value::Number(std::f64::NAN)] => Value::Bool(true), [std::f64::NAN] => true,
&[Value::String("".to_string())] => Value::Bool(false), [""] => false,
&[Value::String("Hello".to_string())] => Value::Bool(true), ["Hello"] => true,
&[Value::String(" ".to_string())] => Value::Bool(true), [" "] => true,
&[Value::String(" 5 ".to_string())] => Value::Bool(true), [" 5 "] => true,
&[Value::String("0".to_string())] => Value::Bool(false), ["0"] => false,
&[Value::String("1".to_string())] => Value::Bool(false), ["1"] => false,
&[Value::String("Infinity".to_string())] => Value::Bool(true), ["Infinity"] => true,
&[Value::String("100a".to_string())] => Value::Bool(true), ["100a"] => true,
&[Value::String("0x10".to_string())] => Value::Bool(false), ["0x10"] => false,
&[Value::String("0xhello".to_string())] => Value::Bool(true), ["0xhello"] => true,
&[Value::String("0x1999999981ffffff".to_string())] => Value::Bool(false), ["0x1999999981ffffff"] => false,
&[Value::String("0xUIXUIDFKHJDF012345678".to_string())] => Value::Bool(true), ["0xUIXUIDFKHJDF012345678"] => true,
&[Value::String("123e-1".to_string())] => Value::Bool(false), ["123e-1"] => false,
&[] => Value::Bool(true) [] => true
); );
test_std!(number_function, number, 19, test_std!(number_function, number, 19,
&[Value::Bool(true)] => Value::Number(1.0), [true] => 1.0,
&[Value::Bool(false)] => Value::Number(0.0), [false] => 0.0,
&[Value::Number(10.0)] => Value::Number(10.0), [10.0] => 10.0,
&[Value::Number(-10.0)] => Value::Number(-10.0), [-10.0] => -10.0,
&[Value::Number(0.0)] => Value::Number(0.0), [0.0] => 0.0,
&[Value::Number(std::f64::INFINITY)] => Value::Number(std::f64::INFINITY), [std::f64::INFINITY] => std::f64::INFINITY,
&[Value::Number(std::f64::NAN)] => Value::Number(std::f64::NAN), [std::f64::NAN] => std::f64::NAN,
&[Value::String("".to_string())] => Value::Number(0.0), [""] => 0.0,
&[Value::String("Hello".to_string())] => Value::Number(std::f64::NAN), ["Hello"] => std::f64::NAN,
&[Value::String(" ".to_string())] => Value::Number(std::f64::NAN), [" "] => std::f64::NAN,
&[Value::String(" 5 ".to_string())] => Value::Number(std::f64::NAN), [" 5 "] => std::f64::NAN,
&[Value::String("0".to_string())] => Value::Number(0.0), ["0"] => 0.0,
&[Value::String("1".to_string())] => Value::Number(1.0), ["1"] => 1.0,
&[Value::String("Infinity".to_string())] => Value::Number(std::f64::NAN), ["Infinity"] => std::f64::NAN,
&[Value::String("100a".to_string())] => Value::Number(std::f64::NAN), ["100a"] => std::f64::NAN,
&[Value::String("0x10".to_string())] => Value::Number(16.0), ["0x10"] => 16.0,
&[Value::String("0xhello".to_string())] => Value::Number(std::f64::NAN), ["0xhello"] => std::f64::NAN,
&[Value::String("123e-1".to_string())] => Value::Number(12.3), ["123e-1"] => 12.3,
&[Value::String("0x1999999981ffffff".to_string())] => Value::Number(-2113929217.0), ["0x1999999981ffffff"] => -2113929217.0,
&[Value::String("0xUIXUIDFKHJDF012345678".to_string())] => Value::Number(std::f64::NAN), ["0xUIXUIDFKHJDF012345678"] => std::f64::NAN,
&[] => Value::Number(0.0) [] => 0.0
); );
} }

View File

@ -125,7 +125,7 @@ mod tests {
use crate::avm1::Error; use crate::avm1::Error;
macro_rules! test_std { macro_rules! test_std {
( $test: ident, $name: expr, $($args: expr => $out: expr),* ) => { ( $test: ident, $name: expr, $([$($arg: expr),*] => $out: expr),* ) => {
#[test] #[test]
fn $test() -> Result<(), Error> { fn $test() -> Result<(), Error> {
with_avm(19, |avm, context, _root| { with_avm(19, |avm, context, _root| {
@ -133,7 +133,12 @@ mod tests {
let function = math.read().get($name, avm, context, math); let function = math.read().get($name, avm, context, math);
$( $(
assert_eq!(function.call(avm, context, math, $args)?, Some($out)); #[allow(unused_mut)]
let mut args: Vec<Value> = Vec::new();
$(
args.push($arg.into());
)*
assert_eq!(function.call(avm, context, math, &args)?, Some($out.into()));
)* )*
Ok(()) Ok(())
@ -143,88 +148,88 @@ mod tests {
} }
test_std!(test_abs, "abs", test_std!(test_abs, "abs",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(-50.0)] => Value::Number(50.0), [-50.0] => 50.0,
&[Value::Number(25.0)] => Value::Number(25.0) [25.0] => 25.0
); );
test_std!(test_acos, "acos", test_std!(test_acos, "acos",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(-1.0)] => Value::Number(f64::acos(-1.0)), [-1.0] => f64::acos(-1.0),
&[Value::Number(0.0)] => Value::Number(f64::acos(0.0)), [0.0] => f64::acos(0.0),
&[Value::Number(1.0)] => Value::Number(f64::acos(1.0)) [1.0] => f64::acos(1.0)
); );
test_std!(test_asin, "asin", test_std!(test_asin, "asin",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(-1.0)] => Value::Number(f64::asin(-1.0)), [-1.0] => f64::asin(-1.0),
&[Value::Number(0.0)] => Value::Number(f64::asin(0.0)), [0.0] => f64::asin(0.0),
&[Value::Number(1.0)] => Value::Number(f64::asin(1.0)) [1.0] => f64::asin(1.0)
); );
test_std!(test_atan, "atan", test_std!(test_atan, "atan",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(-1.0)] => Value::Number(f64::atan(-1.0)), [-1.0] => f64::atan(-1.0),
&[Value::Number(0.0)] => Value::Number(f64::atan(0.0)), [0.0] => f64::atan(0.0),
&[Value::Number(1.0)] => Value::Number(f64::atan(1.0)) [1.0] => f64::atan(1.0)
); );
test_std!(test_ceil, "ceil", test_std!(test_ceil, "ceil",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(12.5)] => Value::Number(13.0) [12.5] => 13.0
); );
test_std!(test_cos, "cos", test_std!(test_cos, "cos",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(0.0)] => Value::Number(1.0), [0.0] => 1.0,
&[Value::Number(std::f64::consts::PI)] => Value::Number(f64::cos(std::f64::consts::PI)) [std::f64::consts::PI] => f64::cos(std::f64::consts::PI)
); );
test_std!(test_exp, "exp", test_std!(test_exp, "exp",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(1.0)] => Value::Number(f64::exp(1.0)), [1.0] => f64::exp(1.0),
&[Value::Number(2.0)] => Value::Number(f64::exp(2.0)) [2.0] => f64::exp(2.0)
); );
test_std!(test_floor, "floor", test_std!(test_floor, "floor",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(12.5)] => Value::Number(12.0) [12.5] => 12.0
); );
test_std!(test_round, "round", test_std!(test_round, "round",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(12.5)] => Value::Number(13.0), [12.5] => 13.0,
&[Value::Number(23.2)] => Value::Number(23.0) [23.2] => 23.0
); );
test_std!(test_sin, "sin", test_std!(test_sin, "sin",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(0.0)] => Value::Number(f64::sin(0.0)), [0.0] => f64::sin(0.0),
&[Value::Number(std::f64::consts::PI / 2.0)] => Value::Number(f64::sin(std::f64::consts::PI / 2.0)) [std::f64::consts::PI / 2.0] => f64::sin(std::f64::consts::PI / 2.0)
); );
test_std!(test_sqrt, "sqrt", test_std!(test_sqrt, "sqrt",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(0.0)] => Value::Number(f64::sqrt(0.0)), [0.0] => f64::sqrt(0.0),
&[Value::Number(5.0)] => Value::Number(f64::sqrt(5.0)) [5.0] => f64::sqrt(5.0)
); );
test_std!(test_tan, "tan", test_std!(test_tan, "tan",
&[] => Value::Number(NAN), [] => NAN,
&[Value::Null] => Value::Number(NAN), [Value::Null] => NAN,
&[Value::Number(0.0)] => Value::Number(f64::tan(0.0)), [0.0] => f64::tan(0.0),
&[Value::Number(1.0)] => Value::Number(f64::tan(1.0)) [1.0] => f64::tan(1.0)
); );
#[test] #[test]

View File

@ -19,7 +19,7 @@ fn default_to_string<'gc>(
_: GcCell<'gc, Object<'gc>>, _: GcCell<'gc, Object<'gc>>,
_: &[Value<'gc>], _: &[Value<'gc>],
) -> Value<'gc> { ) -> Value<'gc> {
Value::String("[Object object]".to_string()) "[Object object]".into()
} }
#[derive(EnumSetType, Debug)] #[derive(EnumSetType, Debug)]
@ -60,19 +60,19 @@ impl<'gc> Property<'gc> {
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut ActionContext<'_, 'gc, '_>, context: &mut ActionContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
new_value: Value<'gc>, new_value: impl Into<Value<'gc>>,
) { ) {
match self { match self {
Property::Virtual { set, .. } => { Property::Virtual { set, .. } => {
if let Some(function) = set { if let Some(function) = set {
function(avm, context, this, &[new_value]); function(avm, context, this, &[new_value.into()]);
} }
} }
Property::Stored { Property::Stored {
value, attributes, .. value, attributes, ..
} => { } => {
if !attributes.contains(ReadOnly) { if !attributes.contains(ReadOnly) {
replace::<Value<'gc>>(value, new_value); replace::<Value<'gc>>(value, new_value.into());
} }
} }
} }
@ -214,7 +214,7 @@ impl<'gc> Object<'gc> {
pub fn set( pub fn set(
&mut self, &mut self,
name: &str, name: &str,
value: Value<'gc>, value: impl Into<Value<'gc>>,
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut ActionContext<'_, 'gc, '_>, context: &mut ActionContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
@ -225,7 +225,7 @@ impl<'gc> Object<'gc> {
} }
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
entry.insert(Property::Stored { entry.insert(Property::Stored {
value, value: value.into(),
attributes: Default::default(), attributes: Default::default(),
}); });
} }
@ -251,14 +251,14 @@ impl<'gc> Object<'gc> {
); );
} }
pub fn force_set<A>(&mut self, name: &str, value: Value<'gc>, attributes: A) pub fn force_set<A>(&mut self, name: &str, value: impl Into<Value<'gc>>, attributes: A)
where where
A: Into<EnumSet<Attribute>>, A: Into<EnumSet<Attribute>>,
{ {
self.values.insert( self.values.insert(
name.to_string(), name.to_string(),
Property::Stored { Property::Stored {
value, value: value.into(),
attributes: attributes.into(), attributes: attributes.into(),
}, },
); );
@ -275,10 +275,7 @@ impl<'gc> Object<'gc> {
{ {
self.force_set( self.force_set(
name, name,
Value::Object(GcCell::allocate( GcCell::allocate(gc_context, Object::native_function(function)),
gc_context,
Object::native_function(function),
)),
attributes, attributes,
) )
} }
@ -432,26 +429,20 @@ mod tests {
#[test] #[test]
fn test_set_get() { fn test_set_get() {
with_object(0, |avm, context, object| { with_object(0, |avm, context, object| {
object.write(context.gc_context).force_set( object
"forced", .write(context.gc_context)
Value::String("forced".to_string()), .force_set("forced", "forced", EnumSet::empty());
EnumSet::empty(), object
); .write(context.gc_context)
object.write(context.gc_context).set( .set("natural", "natural", avm, context, object);
"natural",
Value::String("natural".to_string()),
avm,
context,
object,
);
assert_eq!( assert_eq!(
object.read().get("forced", avm, context, object), object.read().get("forced", avm, context, object),
Value::String("forced".to_string()) "forced".into()
); );
assert_eq!( assert_eq!(
object.read().get("natural", avm, context, object), object.read().get("natural", avm, context, object),
Value::String("natural".to_string()) "natural".into()
); );
}) })
} }
@ -459,39 +450,27 @@ mod tests {
#[test] #[test]
fn test_set_readonly() { fn test_set_readonly() {
with_object(0, |avm, context, object| { with_object(0, |avm, context, object| {
object.write(context.gc_context).force_set( object
"normal", .write(context.gc_context)
Value::String("initial".to_string()), .force_set("normal", "initial", EnumSet::empty());
EnumSet::empty(), object
); .write(context.gc_context)
object.write(context.gc_context).force_set( .force_set("readonly", "initial", ReadOnly);
"readonly",
Value::String("initial".to_string()),
ReadOnly,
);
object.write(context.gc_context).set( object
"normal", .write(context.gc_context)
Value::String("replaced".to_string()), .set("normal", "replaced", avm, context, object);
avm, object
context, .write(context.gc_context)
object, .set("readonly", "replaced", avm, context, object);
);
object.write(context.gc_context).set(
"readonly",
Value::String("replaced".to_string()),
avm,
context,
object,
);
assert_eq!( assert_eq!(
object.read().get("normal", avm, context, object), object.read().get("normal", avm, context, object),
Value::String("replaced".to_string()) "replaced".into()
); );
assert_eq!( assert_eq!(
object.read().get("readonly", avm, context, object), object.read().get("readonly", avm, context, object),
Value::String("initial".to_string()) "initial".into()
); );
}) })
} }
@ -499,30 +478,24 @@ mod tests {
#[test] #[test]
fn test_deletable_not_readonly() { fn test_deletable_not_readonly() {
with_object(0, |avm, context, object| { with_object(0, |avm, context, object| {
object.write(context.gc_context).force_set( object
"test", .write(context.gc_context)
Value::String("initial".to_string()), .force_set("test", "initial", DontDelete);
DontDelete,
);
assert_eq!(object.write(context.gc_context).delete("test"), false); assert_eq!(object.write(context.gc_context).delete("test"), false);
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Value::String("initial".to_string()) "initial".into()
); );
object.write(context.gc_context).set( object
"test", .write(context.gc_context)
Value::String("replaced".to_string()), .set("test", "replaced", avm, context, object);
avm,
context,
object,
);
assert_eq!(object.write(context.gc_context).delete("test"), false); assert_eq!(object.write(context.gc_context).delete("test"), false);
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Value::String("replaced".to_string()) "replaced".into()
); );
}) })
} }
@ -530,8 +503,7 @@ mod tests {
#[test] #[test]
fn test_virtual_get() { fn test_virtual_get() {
with_object(0, |avm, context, object| { with_object(0, |avm, context, object| {
let getter: NativeFunction = let getter: NativeFunction = |_avm, _context, _this, _args| "Virtual!".into();
|_avm, _context, _this, _args| Value::String("Virtual!".to_string());
object.write(context.gc_context).force_set_virtual( object.write(context.gc_context).force_set_virtual(
"test", "test",
getter, getter,
@ -541,20 +513,16 @@ mod tests {
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Value::String("Virtual!".to_string()) "Virtual!".into()
); );
// This set should do nothing // This set should do nothing
object.write(context.gc_context).set( object
"test", .write(context.gc_context)
Value::String("Ignored!".to_string()), .set("test", "Ignored!", avm, context, object);
avm,
context,
object,
);
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Value::String("Virtual!".to_string()) "Virtual!".into()
); );
}) })
} }
@ -562,8 +530,7 @@ mod tests {
#[test] #[test]
fn test_delete() { fn test_delete() {
with_object(0, |avm, context, object| { with_object(0, |avm, context, object| {
let getter: NativeFunction = let getter: NativeFunction = |_avm, _context, _this, _args| "Virtual!".into();
|_avm, _context, _this, _args| Value::String("Virtual!".to_string());
object.write(context.gc_context).force_set_virtual( object.write(context.gc_context).force_set_virtual(
"virtual", "virtual",
@ -577,16 +544,12 @@ mod tests {
None, None,
DontDelete, DontDelete,
); );
object.write(context.gc_context).force_set( object
"stored", .write(context.gc_context)
Value::String("Stored!".to_string()), .force_set("stored", "Stored!", EnumSet::empty());
EnumSet::empty(), object
); .write(context.gc_context)
object.write(context.gc_context).force_set( .force_set("stored_un", "Stored!", DontDelete);
"stored_un",
Value::String("Stored!".to_string()),
DontDelete,
);
assert_eq!(object.write(context.gc_context).delete("virtual"), true); assert_eq!(object.write(context.gc_context).delete("virtual"), true);
assert_eq!(object.write(context.gc_context).delete("virtual_un"), false); assert_eq!(object.write(context.gc_context).delete("virtual_un"), false);
@ -603,7 +566,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.read().get("virtual_un", avm, context, object), object.read().get("virtual_un", avm, context, object),
Value::String("Virtual!".to_string()) "Virtual!".into()
); );
assert_eq!( assert_eq!(
object.read().get("stored", avm, context, object), object.read().get("stored", avm, context, object),
@ -611,7 +574,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.read().get("stored_un", avm, context, object), object.read().get("stored_un", avm, context, object),
Value::String("Stored!".to_string()) "Stored!".into()
); );
}) })
} }

View File

@ -301,7 +301,7 @@ impl<'gc> Scope<'gc> {
/// stored (e.g. not virtual) properties on the lowest object in the scope /// stored (e.g. not virtual) properties on the lowest object in the scope
/// chain. As a result, this function always force sets a property on the /// chain. As a result, this function always force sets a property on the
/// local object and does not traverse the scope chain. /// local object and does not traverse the scope chain.
pub fn define(&self, name: &str, value: Value<'gc>, mc: MutationContext<'gc, '_>) { pub fn define(&self, name: &str, value: impl Into<Value<'gc>>, mc: MutationContext<'gc, '_>) {
self.locals_mut(mc).force_set(name, value, EnumSet::empty()); self.locals_mut(mc).force_set(name, value, EnumSet::empty());
} }

View File

@ -13,6 +13,60 @@ pub enum Value<'gc> {
Object(GcCell<'gc, Object<'gc>>), Object(GcCell<'gc, Object<'gc>>),
} }
impl<'gc> From<String> for Value<'gc> {
fn from(string: String) -> Self {
Value::String(string)
}
}
impl<'gc> From<&str> for Value<'gc> {
fn from(string: &str) -> Self {
Value::String(string.to_owned())
}
}
impl<'gc> From<bool> for Value<'gc> {
fn from(value: bool) -> Self {
Value::Bool(value)
}
}
impl<'gc> From<GcCell<'gc, Object<'gc>>> for Value<'gc> {
fn from(object: GcCell<'gc, Object<'gc>>) -> Self {
Value::Object(object)
}
}
impl<'gc> From<f64> for Value<'gc> {
fn from(value: f64) -> Self {
Value::Number(value)
}
}
impl<'gc> From<f32> for Value<'gc> {
fn from(value: f32) -> Self {
Value::Number(f64::from(value))
}
}
impl<'gc> From<u8> for Value<'gc> {
fn from(value: u8) -> Self {
Value::Number(f64::from(value))
}
}
impl<'gc> From<i32> for Value<'gc> {
fn from(value: i32) -> Self {
Value::Number(f64::from(value))
}
}
impl<'gc> From<u32> for Value<'gc> {
fn from(value: u32) -> Self {
Value::Number(f64::from(value))
}
}
unsafe impl<'gc> gc_arena::Collect for Value<'gc> { unsafe impl<'gc> gc_arena::Collect for Value<'gc> {
fn trace(&self, cc: gc_arena::CollectionContext) { fn trace(&self, cc: gc_arena::CollectionContext) {
if let Value::Object(object) = self { if let Value::Object(object) = self {