Implemented more Math methods (with tests)
This commit is contained in:
parent
a4cdbc4f70
commit
4ba12517d9
|
@ -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<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)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue