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 std::f64::NAN;
fn abs<'gc>(
_context: &mut ActionContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>],
) -> Value<'gc> {
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(input.as_number().abs())
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())
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 {
Value::Number(NAN)
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<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
F: FnOnce(&mut ActionContext),
F: FnOnce(&mut ActionContext) -> R,
{
rootless_arena(|gc_context| {
let movie_clip: Box<dyn DisplayObject> = 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)
);
});
}