diff --git a/core/src/avm1/globals/math.rs b/core/src/avm1/globals/math.rs index 074c4dc0c..18f110ee8 100644 --- a/core/src/avm1/globals/math.rs +++ b/core/src/avm1/globals/math.rs @@ -2,50 +2,103 @@ use crate::avm1::{ActionContext, Object, Value}; use gc_arena::{GcCell, MutationContext}; use std::f64::NAN; -fn abs<'gc>( - _context: &mut ActionContext<'_, 'gc, '_>, - _this: GcCell<'gc, Object<'gc>>, - args: &[Value<'gc>], -) -> Value<'gc> { - if let Some(input) = args.get(0) { - Value::Number(input.as_number().abs()) - } else { - Value::Number(NAN) - } +macro_rules! wrap_std { + ( $object: ident, $gc_context: ident, $($name:expr => $std:path),* ) => {{ + $( + $object.set_function( + $name, + |_context, _this, args| -> Value<'gc> { + if let Some(input) = args.get(0) { + Value::Number($std(input.as_number())) + } else { + Value::Number(NAN) + } + }, + $gc_context, + ); + )* + }}; } -fn round<'gc>( +fn atan2<'gc>( _context: &mut ActionContext<'_, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], ) -> Value<'gc> { - if let Some(input) = args.get(0) { - Value::Number(input.as_number().round()) - } else { - Value::Number(NAN) + if let Some(y) = args.get(0) { + if let Some(x) = args.get(1) { + return Value::Number(y.as_number().atan2(x.as_number())); + } else { + return Value::Number(y.as_number().atan2(0.0)); + } } + Value::Number(NAN) } pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> { let mut math = Object::object(gc_context); - math.set_function("abs", abs, gc_context); - math.set_function("round", round, gc_context); + math.set("E", Value::Number(std::f64::consts::E)); + math.set("LN10", Value::Number(std::f64::consts::LN_10)); + math.set("LN2", Value::Number(std::f64::consts::LN_2)); + math.set("LOG10E", Value::Number(std::f64::consts::LOG10_E)); + math.set("LOG2E", Value::Number(std::f64::consts::LOG2_E)); + math.set("PI", Value::Number(std::f64::consts::PI)); + math.set("SQRT1_2", Value::Number(std::f64::consts::FRAC_1_SQRT_2)); + math.set("SQRT2", Value::Number(std::f64::consts::SQRT_2)); + + wrap_std!(math, gc_context, + "abs" => f64::abs, + "acos" => f64::acos, + "asin" => f64::asin, + "atan" => f64::atan, + "ceil" => f64::ceil, + "cos" => f64::cos, + "exp" => f64::exp, + "floor" => f64::floor, + "round" => f64::round, + "sin" => f64::sin, + "sqrt" => f64::sqrt, + "tan" => f64::tan + ); + + math.set_function("atan2", atan2, gc_context); GcCell::allocate(gc_context, math) } #[cfg(test)] +#[allow(clippy::unreadable_literal)] +#[allow(clippy::approx_constant)] mod tests { use super::*; + use crate::avm1::Error; use crate::backend::audio::NullAudioBackend; use crate::display_object::DisplayObject; use crate::movie_clip::MovieClip; use gc_arena::rootless_arena; - fn with_avm(test: F) + macro_rules! test_std { + ( $test: ident, $name: expr, $($args: expr => $out: expr),* ) => { + #[test] + fn $test() -> Result<(), Error> { + with_avm(|context| { + let math = create(context.gc_context); + let function = math.read().get($name); + + $( + assert_eq!(function.call(context, math, $args)?, $out); + )* + + Ok(()) + }) + } + }; +} + + fn with_avm(test: F) -> R where - F: FnOnce(&mut ActionContext), + F: FnOnce(&mut ActionContext) -> R, { rootless_arena(|gc_context| { let movie_clip: Box = Box::new(MovieClip::new(gc_context)); @@ -58,80 +111,138 @@ mod tests { active_clip: root, audio: &mut NullAudioBackend::new(), }; - test(&mut context); - }); + test(&mut context) + }) } + test_std!(test_abs, "abs", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(-50.0)] => Value::Number(50.0), + &[Value::Number(25.0)] => Value::Number(25.0) + ); + + test_std!(test_acos, "acos", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(-1.0)] => Value::Number(3.141592653589793), + &[Value::Number(0.0)] => Value::Number(1.5707963267948966), + &[Value::Number(1.0)] => Value::Number(0.0) + ); + + test_std!(test_asin, "asin", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(-1.0)] => Value::Number(-1.5707963267948966), + &[Value::Number(0.0)] => Value::Number(0.0), + &[Value::Number(1.0)] => Value::Number(1.5707963267948966) + ); + + test_std!(test_atan, "atan", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(-1.0)] => Value::Number(-0.7853981633974483), + &[Value::Number(0.0)] => Value::Number(0.0), + &[Value::Number(1.0)] => Value::Number(0.7853981633974483) + ); + + test_std!(test_ceil, "ceil", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(12.5)] => Value::Number(13.0) + ); + + test_std!(test_cos, "cos", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(0.0)] => Value::Number(1.0), + &[Value::Number(std::f64::consts::PI)] => Value::Number(-1.0) + ); + + test_std!(test_exp, "exp", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(1.0)] => Value::Number(2.718281828459045), + &[Value::Number(2.0)] => Value::Number(7.38905609893065) + ); + + test_std!(test_floor, "floor", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(12.5)] => Value::Number(12.0) + ); + + test_std!(test_round, "round", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(12.5)] => Value::Number(13.0), + &[Value::Number(23.2)] => Value::Number(23.0) + ); + + test_std!(test_sin, "sin", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(0.0)] => Value::Number(0.0), + &[Value::Number(std::f64::consts::PI / 2.0)] => Value::Number(1.0) + ); + + test_std!(test_sqrt, "sqrt", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(0.0)] => Value::Number(0.0), + &[Value::Number(5.0)] => Value::Number(2.23606797749979) + ); + + test_std!(test_tan, "tan", + &[] => Value::Number(NAN), + &[Value::Null] => Value::Number(NAN), + &[Value::Number(0.0)] => Value::Number(0.0), + &[Value::Number(1.0)] => Value::Number(1.5574077246549023) + ); + #[test] - fn test_abs_nan() { + fn test_atan2_nan() { with_avm(|context| { let math = GcCell::allocate(context.gc_context, create(context.gc_context)); - assert_eq!(abs(context, *math.read(), &[]), Value::Number(NAN)); + assert_eq!(atan2(context, *math.read(), &[]), Value::Number(NAN)); assert_eq!( - abs(context, *math.read(), &[Value::Number(NAN)]), + atan2(context, *math.read(), &[Value::Number(1.0), Value::Null]), Value::Number(NAN) ); assert_eq!( - abs(context, *math.read(), &[Value::String("".to_string())]), + atan2( + context, + *math.read(), + &[Value::Number(1.0), Value::Undefined] + ), + Value::Number(NAN) + ); + assert_eq!( + atan2( + context, + *math.read(), + &[Value::Undefined, Value::Number(1.0)] + ), Value::Number(NAN) ); }); } #[test] - fn test_abs_valid() { + fn test_atan2_valid() { with_avm(|context| { let math = GcCell::allocate(context.gc_context, create(context.gc_context)); assert_eq!( - abs(context, *math.read(), &[Value::Number(-50.0)]), - Value::Number(50.0) + atan2(context, *math.read(), &[Value::Number(10.0)]), + Value::Number(std::f64::consts::FRAC_PI_2) ); assert_eq!( - abs(context, *math.read(), &[Value::Number(50.0)]), - Value::Number(50.0) - ); - assert_eq!( - abs(context, *math.read(), &[Value::Bool(true)]), - Value::Number(1.0) - ); - assert_eq!( - abs(context, *math.read(), &[Value::String("-10".to_string())]), - Value::Number(10.0) - ); - }); - } - - #[test] - fn test_round_nan() { - with_avm(|context| { - let math = GcCell::allocate(context.gc_context, create(context.gc_context)); - assert_eq!(round(context, *math.read(), &[]), Value::Number(NAN)); - assert_eq!( - round(context, *math.read(), &[Value::Number(NAN)]), - Value::Number(NAN) - ); - assert_eq!( - round(context, *math.read(), &[Value::String("".to_string())]), - Value::Number(NAN) - ); - }); - } - - #[test] - fn test_round_valid() { - with_avm(|context| { - let math = GcCell::allocate(context.gc_context, create(context.gc_context)); - assert_eq!( - round(context, *math.read(), &[Value::Number(0.4)]), - Value::Number(0.0) - ); - assert_eq!( - round(context, *math.read(), &[Value::Number(1.5)]), - Value::Number(2.0) - ); - assert_eq!( - round(context, *math.read(), &[Value::String("-5.4".to_string())]), - Value::Number(-5.0) + atan2( + context, + *math.read(), + &[Value::Number(1.0), Value::Number(2.0)] + ), + Value::Number(0.4636476090008061) ); }); }