Implemented more Math methods (with tests)

This commit is contained in:
Nathan Adams 2019-09-03 00:49:19 +02:00
parent a4cdbc4f70
commit 4ba12517d9
1 changed files with 184 additions and 73 deletions

View File

@ -2,50 +2,103 @@ use crate::avm1::{ActionContext, Object, Value};
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
use std::f64::NAN; use std::f64::NAN;
fn abs<'gc>( macro_rules! wrap_std {
_context: &mut ActionContext<'_, 'gc, '_>, ( $object: ident, $gc_context: ident, $($name:expr => $std:path),* ) => {{
_this: GcCell<'gc, Object<'gc>>, $(
args: &[Value<'gc>], $object.set_function(
) -> Value<'gc> { $name,
if let Some(input) = args.get(0) { |_context, _this, args| -> Value<'gc> {
Value::Number(input.as_number().abs()) if let Some(input) = args.get(0) {
} else { Value::Number($std(input.as_number()))
Value::Number(NAN) } else {
} Value::Number(NAN)
}
},
$gc_context,
);
)*
}};
} }
fn round<'gc>( fn atan2<'gc>(
_context: &mut ActionContext<'_, 'gc, '_>, _context: &mut ActionContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Value<'gc> { ) -> Value<'gc> {
if let Some(input) = args.get(0) { if let Some(y) = args.get(0) {
Value::Number(input.as_number().round()) if let Some(x) = args.get(1) {
} else { return Value::Number(y.as_number().atan2(x.as_number()));
Value::Number(NAN) } 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>> { pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> {
let mut math = Object::object(gc_context); let mut math = Object::object(gc_context);
math.set_function("abs", abs, gc_context); math.set("E", Value::Number(std::f64::consts::E));
math.set_function("round", round, gc_context); 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) GcCell::allocate(gc_context, math)
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unreadable_literal)]
#[allow(clippy::approx_constant)]
mod tests { mod tests {
use super::*; use super::*;
use crate::avm1::Error;
use crate::backend::audio::NullAudioBackend; use crate::backend::audio::NullAudioBackend;
use crate::display_object::DisplayObject; use crate::display_object::DisplayObject;
use crate::movie_clip::MovieClip; use crate::movie_clip::MovieClip;
use gc_arena::rootless_arena; use gc_arena::rootless_arena;
fn with_avm<F>(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<F, R>(test: F) -> R
where where
F: FnOnce(&mut ActionContext), F: FnOnce(&mut ActionContext) -> R,
{ {
rootless_arena(|gc_context| { rootless_arena(|gc_context| {
let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(gc_context)); let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(gc_context));
@ -58,80 +111,138 @@ mod tests {
active_clip: root, active_clip: root,
audio: &mut NullAudioBackend::new(), 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] #[test]
fn test_abs_nan() { fn test_atan2_nan() {
with_avm(|context| { with_avm(|context| {
let math = GcCell::allocate(context.gc_context, create(context.gc_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!( assert_eq!(
abs(context, *math.read(), &[Value::Number(NAN)]), atan2(context, *math.read(), &[Value::Number(1.0), Value::Null]),
Value::Number(NAN) Value::Number(NAN)
); );
assert_eq!( 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) Value::Number(NAN)
); );
}); });
} }
#[test] #[test]
fn test_abs_valid() { fn test_atan2_valid() {
with_avm(|context| { with_avm(|context| {
let math = GcCell::allocate(context.gc_context, create(context.gc_context)); let math = GcCell::allocate(context.gc_context, create(context.gc_context));
assert_eq!( assert_eq!(
abs(context, *math.read(), &[Value::Number(-50.0)]), atan2(context, *math.read(), &[Value::Number(10.0)]),
Value::Number(50.0) Value::Number(std::f64::consts::FRAC_PI_2)
); );
assert_eq!( assert_eq!(
abs(context, *math.read(), &[Value::Number(50.0)]), atan2(
Value::Number(50.0) context,
); *math.read(),
assert_eq!( &[Value::Number(1.0), Value::Number(2.0)]
abs(context, *math.read(), &[Value::Bool(true)]), ),
Value::Number(1.0) Value::Number(0.4636476090008061)
);
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)
); );
}); });
} }