core: Merge #91, add some global conversion functions
This commit is contained in:
commit
6bd4ed22a1
|
@ -21,6 +21,8 @@ mod globals;
|
|||
pub mod movie_clip;
|
||||
pub mod object;
|
||||
mod scope;
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
mod value;
|
||||
|
||||
use activation::Activation;
|
||||
|
@ -1207,7 +1209,7 @@ impl<'gc> Avm1<'gc> {
|
|||
reader: &mut Reader<'_>,
|
||||
) -> Result<(), Error> {
|
||||
let val = self.pop()?;
|
||||
if val.as_bool() {
|
||||
if val.as_bool(self.current_swf_version()) {
|
||||
reader.seek(jump_offset.into());
|
||||
}
|
||||
Ok(())
|
||||
|
@ -1650,8 +1652,8 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
fn action_start_drag(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
||||
let _target = self.pop()?;
|
||||
let _lock_center = self.pop()?.as_bool();
|
||||
let constrain = self.pop()?.as_bool();
|
||||
let _lock_center = self.pop()?.as_bool(self.current_swf_version());
|
||||
let constrain = self.pop()?.as_bool(self.current_swf_version());
|
||||
if constrain {
|
||||
let _y2 = self.pop()?;
|
||||
let _x2 = self.pop()?;
|
||||
|
|
|
@ -50,16 +50,59 @@ pub fn random<'gc>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn boolean<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
_action_context: &mut ActionContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Value<'gc> {
|
||||
if let Some(val) = args.get(0) {
|
||||
Value::Bool(val.as_bool(avm.current_swf_version()))
|
||||
} else {
|
||||
Value::Bool(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_action_context: &mut ActionContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Value<'gc> {
|
||||
if let Some(val) = args.get(0) {
|
||||
Value::Number(val.as_number())
|
||||
} else {
|
||||
Value::Number(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_nan<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_action_context: &mut ActionContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Value<'gc> {
|
||||
if let Some(val) = args.get(0) {
|
||||
Value::Bool(val.as_number().is_nan())
|
||||
} else {
|
||||
Value::Bool(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_globals<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> {
|
||||
let mut globals = Object::object(gc_context);
|
||||
|
||||
globals.force_set_function("isNaN", is_nan, gc_context, EnumSet::empty());
|
||||
globals.force_set_function("Boolean", boolean, gc_context, EnumSet::empty());
|
||||
globals.force_set(
|
||||
"Math",
|
||||
Value::Object(math::create(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("random", random, gc_context, EnumSet::empty());
|
||||
|
||||
globals.force_set("NaN", Value::Number(std::f64::NAN), EnumSet::empty());
|
||||
globals.force_set(
|
||||
"Infinity",
|
||||
|
@ -69,3 +112,107 @@ pub fn create_globals<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc>
|
|||
|
||||
globals
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unreadable_literal)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::avm1::test_utils::with_avm;
|
||||
use crate::avm1::Error;
|
||||
|
||||
macro_rules! test_std {
|
||||
( $test: ident, $fun: expr, $version: expr, $($args: expr => $out: expr),* ) => {
|
||||
#[test]
|
||||
fn $test() -> Result<(), Error> {
|
||||
with_avm($version, |avm, context, this| {
|
||||
|
||||
$(
|
||||
assert_eq!($fun(avm, context, this, $args), $out);
|
||||
)*
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_std!(boolean_function, boolean, 19,
|
||||
&[Value::Bool(true)] => Value::Bool(true),
|
||||
&[Value::Bool(false)] => Value::Bool(false),
|
||||
&[Value::Number(10.0)] => Value::Bool(true),
|
||||
&[Value::Number(-10.0)] => Value::Bool(true),
|
||||
&[Value::Number(0.0)] => Value::Bool(false),
|
||||
&[Value::Number(std::f64::INFINITY)] => Value::Bool(true),
|
||||
&[Value::Number(std::f64::NAN)] => Value::Bool(false),
|
||||
&[Value::String("".to_string())] => Value::Bool(false),
|
||||
&[Value::String("Hello".to_string())] => Value::Bool(true),
|
||||
&[Value::String(" ".to_string())] => Value::Bool(true),
|
||||
&[Value::String("0".to_string())] => Value::Bool(true),
|
||||
&[Value::String("1".to_string())] => Value::Bool(true),
|
||||
&[] => Value::Bool(false)
|
||||
);
|
||||
|
||||
test_std!(boolean_function_swf6, boolean, 6,
|
||||
&[Value::Bool(true)] => Value::Bool(true),
|
||||
&[Value::Bool(false)] => Value::Bool(false),
|
||||
&[Value::Number(10.0)] => Value::Bool(true),
|
||||
&[Value::Number(-10.0)] => Value::Bool(true),
|
||||
&[Value::Number(0.0)] => Value::Bool(false),
|
||||
&[Value::Number(std::f64::INFINITY)] => Value::Bool(true),
|
||||
&[Value::Number(std::f64::NAN)] => Value::Bool(false),
|
||||
&[Value::String("".to_string())] => Value::Bool(false),
|
||||
&[Value::String("Hello".to_string())] => Value::Bool(false),
|
||||
&[Value::String(" ".to_string())] => Value::Bool(false),
|
||||
&[Value::String("0".to_string())] => Value::Bool(false),
|
||||
&[Value::String("1".to_string())] => Value::Bool(true),
|
||||
&[] => Value::Bool(false)
|
||||
);
|
||||
|
||||
test_std!(is_nan_function, is_nan, 19,
|
||||
&[Value::Bool(true)] => Value::Bool(false),
|
||||
&[Value::Bool(false)] => Value::Bool(false),
|
||||
&[Value::Number(10.0)] => Value::Bool(false),
|
||||
&[Value::Number(-10.0)] => Value::Bool(false),
|
||||
&[Value::Number(0.0)] => Value::Bool(false),
|
||||
&[Value::Number(std::f64::INFINITY)] => Value::Bool(false),
|
||||
&[Value::Number(std::f64::NAN)] => Value::Bool(true),
|
||||
&[Value::String("".to_string())] => Value::Bool(false),
|
||||
&[Value::String("Hello".to_string())] => Value::Bool(true),
|
||||
&[Value::String(" ".to_string())] => Value::Bool(true),
|
||||
&[Value::String(" 5 ".to_string())] => Value::Bool(true),
|
||||
&[Value::String("0".to_string())] => Value::Bool(false),
|
||||
&[Value::String("1".to_string())] => Value::Bool(false),
|
||||
&[Value::String("Infinity".to_string())] => Value::Bool(true),
|
||||
&[Value::String("100a".to_string())] => Value::Bool(true),
|
||||
&[Value::String("0x10".to_string())] => Value::Bool(false),
|
||||
&[Value::String("0xhello".to_string())] => Value::Bool(true),
|
||||
&[Value::String("0x1999999981ffffff".to_string())] => Value::Bool(false),
|
||||
&[Value::String("0xUIXUIDFKHJDF012345678".to_string())] => Value::Bool(true),
|
||||
&[Value::String("123e-1".to_string())] => Value::Bool(false),
|
||||
&[] => Value::Bool(true)
|
||||
);
|
||||
|
||||
test_std!(number_function, number, 19,
|
||||
&[Value::Bool(true)] => Value::Number(1.0),
|
||||
&[Value::Bool(false)] => Value::Number(0.0),
|
||||
&[Value::Number(10.0)] => Value::Number(10.0),
|
||||
&[Value::Number(-10.0)] => Value::Number(-10.0),
|
||||
&[Value::Number(0.0)] => Value::Number(0.0),
|
||||
&[Value::Number(std::f64::INFINITY)] => Value::Number(std::f64::INFINITY),
|
||||
&[Value::Number(std::f64::NAN)] => Value::Number(std::f64::NAN),
|
||||
&[Value::String("".to_string())] => Value::Number(0.0),
|
||||
&[Value::String("Hello".to_string())] => Value::Number(std::f64::NAN),
|
||||
&[Value::String(" ".to_string())] => Value::Number(std::f64::NAN),
|
||||
&[Value::String(" 5 ".to_string())] => Value::Number(std::f64::NAN),
|
||||
&[Value::String("0".to_string())] => Value::Number(0.0),
|
||||
&[Value::String("1".to_string())] => Value::Number(1.0),
|
||||
&[Value::String("Infinity".to_string())] => Value::Number(std::f64::NAN),
|
||||
&[Value::String("100a".to_string())] => Value::Number(std::f64::NAN),
|
||||
&[Value::String("0x10".to_string())] => Value::Number(16.0),
|
||||
&[Value::String("0xhello".to_string())] => Value::Number(std::f64::NAN),
|
||||
&[Value::String("123e-1".to_string())] => Value::Number(12.3),
|
||||
&[Value::String("0x1999999981ffffff".to_string())] => Value::Number(-2113929217.0),
|
||||
&[Value::String("0xUIXUIDFKHJDF012345678".to_string())] => Value::Number(std::f64::NAN),
|
||||
&[] => Value::Number(0.0)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -121,20 +121,14 @@ pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::test_utils::with_avm;
|
||||
use crate::avm1::Error;
|
||||
use crate::backend::audio::NullAudioBackend;
|
||||
use crate::backend::navigator::NullNavigatorBackend;
|
||||
use crate::display_object::DisplayObject;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use gc_arena::rootless_arena;
|
||||
use rand::{rngs::SmallRng, SeedableRng};
|
||||
|
||||
macro_rules! test_std {
|
||||
( $test: ident, $name: expr, $($args: expr => $out: expr),* ) => {
|
||||
#[test]
|
||||
fn $test() -> Result<(), Error> {
|
||||
with_avm(19, |avm, context| {
|
||||
with_avm(19, |avm, context, _root| {
|
||||
let math = create(context.gc_context);
|
||||
let function = math.read().get($name, avm, context, math);
|
||||
|
||||
|
@ -148,39 +142,6 @@ mod tests {
|
|||
};
|
||||
}
|
||||
|
||||
fn with_avm<F, R>(swf_version: u8, test: F) -> R
|
||||
where
|
||||
F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut ActionContext<'a, 'gc, '_>) -> R,
|
||||
{
|
||||
rootless_arena(|gc_context| {
|
||||
let mut avm = Avm1::new(gc_context, swf_version);
|
||||
let movie_clip: Box<dyn DisplayObject> =
|
||||
Box::new(MovieClip::new(swf_version, gc_context));
|
||||
let root = GcCell::allocate(gc_context, movie_clip);
|
||||
let mut context = ActionContext {
|
||||
gc_context,
|
||||
global_time: 0,
|
||||
player_version: 32,
|
||||
root,
|
||||
start_clip: root,
|
||||
active_clip: root,
|
||||
target_clip: Some(root),
|
||||
target_path: Value::Undefined,
|
||||
rng: &mut SmallRng::from_seed([0u8; 16]),
|
||||
audio: &mut NullAudioBackend::new(),
|
||||
navigator: &mut NullNavigatorBackend::new(),
|
||||
};
|
||||
|
||||
let globals = avm.global_object_cell();
|
||||
avm.insert_stack_frame(
|
||||
Activation::from_nothing(swf_version, globals, gc_context),
|
||||
&mut context,
|
||||
);
|
||||
|
||||
test(&mut avm, &mut context)
|
||||
})
|
||||
}
|
||||
|
||||
test_std!(test_abs, "abs",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
|
@ -268,7 +229,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_atan2_nan() {
|
||||
with_avm(19, |avm, context| {
|
||||
with_avm(19, |avm, context, _root| {
|
||||
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
||||
assert_eq!(atan2(avm, context, *math.read(), &[]), Value::Number(NAN));
|
||||
assert_eq!(
|
||||
|
@ -303,7 +264,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_atan2_valid() {
|
||||
with_avm(19, |avm, context| {
|
||||
with_avm(19, |avm, context, _root| {
|
||||
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
||||
assert_eq!(
|
||||
atan2(avm, context, *math.read(), &[Value::Number(10.0)]),
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::{ActionContext, Avm1, Object, Value};
|
||||
use crate::backend::audio::NullAudioBackend;
|
||||
use crate::backend::navigator::NullNavigatorBackend;
|
||||
use crate::display_object::DisplayObject;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use gc_arena::{rootless_arena, GcCell};
|
||||
use rand::{rngs::SmallRng, SeedableRng};
|
||||
|
||||
pub fn with_avm<F, R>(swf_version: u8, test: F) -> R
|
||||
where
|
||||
F: for<'a, 'gc> FnOnce(
|
||||
&mut Avm1<'gc>,
|
||||
&mut ActionContext<'a, 'gc, '_>,
|
||||
GcCell<'gc, Object<'gc>>,
|
||||
) -> R,
|
||||
{
|
||||
rootless_arena(|gc_context| {
|
||||
let mut avm = Avm1::new(gc_context, swf_version);
|
||||
let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(swf_version, gc_context));
|
||||
let root = GcCell::allocate(gc_context, movie_clip);
|
||||
let mut context = ActionContext {
|
||||
gc_context,
|
||||
global_time: 0,
|
||||
player_version: 32,
|
||||
root,
|
||||
start_clip: root,
|
||||
active_clip: root,
|
||||
target_clip: Some(root),
|
||||
target_path: Value::Undefined,
|
||||
rng: &mut SmallRng::from_seed([0u8; 16]),
|
||||
audio: &mut NullAudioBackend::new(),
|
||||
navigator: &mut NullNavigatorBackend::new(),
|
||||
};
|
||||
|
||||
let globals = avm.global_object_cell();
|
||||
avm.insert_stack_frame(
|
||||
Activation::from_nothing(swf_version, globals, gc_context),
|
||||
&mut context,
|
||||
);
|
||||
|
||||
let this = root.read().object().as_object().unwrap().to_owned();
|
||||
|
||||
test(&mut avm, &mut context, this)
|
||||
})
|
||||
}
|
|
@ -73,7 +73,36 @@ impl<'gc> Value<'gc> {
|
|||
Value::Bool(false) => 0.0,
|
||||
Value::Bool(true) => 1.0,
|
||||
Value::Number(v) => *v,
|
||||
Value::String(v) => v.parse().unwrap_or(NAN), // TODO(Herschel): Handle Infinity/etc.?
|
||||
Value::String(v) => match v.as_str() {
|
||||
v if v.starts_with("0x") => {
|
||||
let mut n: u32 = 0;
|
||||
for c in v[2..].bytes() {
|
||||
n = n.wrapping_shl(4);
|
||||
n |= match c {
|
||||
b'0' => 0,
|
||||
b'1' => 1,
|
||||
b'2' => 2,
|
||||
b'3' => 3,
|
||||
b'4' => 4,
|
||||
b'5' => 5,
|
||||
b'6' => 6,
|
||||
b'7' => 7,
|
||||
b'8' => 8,
|
||||
b'9' => 9,
|
||||
b'a' | b'A' => 10,
|
||||
b'b' | b'B' => 11,
|
||||
b'c' | b'C' => 12,
|
||||
b'd' | b'D' => 13,
|
||||
b'e' | b'E' => 14,
|
||||
b'f' | b'F' => 15,
|
||||
_ => return NAN,
|
||||
}
|
||||
}
|
||||
f64::from(n as i32)
|
||||
}
|
||||
"" => 0.0,
|
||||
_ => v.parse().unwrap_or(NAN),
|
||||
},
|
||||
Value::Object(_object) => {
|
||||
log::error!("Unimplemented: Object ToNumber");
|
||||
0.0
|
||||
|
@ -103,11 +132,19 @@ impl<'gc> Value<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> bool {
|
||||
match *self {
|
||||
Value::Bool(v) => v,
|
||||
Value::Number(v) => v != 0.0,
|
||||
// TODO(Herschel): Value::String(v) => ??
|
||||
pub fn as_bool(&self, swf_version: u8) -> bool {
|
||||
match self {
|
||||
Value::Bool(v) => *v,
|
||||
Value::Number(v) => !v.is_nan() && *v != 0.0,
|
||||
Value::String(v) => {
|
||||
if swf_version >= 7 {
|
||||
!v.is_empty()
|
||||
} else {
|
||||
let num = v.parse().unwrap_or(0.0);
|
||||
num != 0.0
|
||||
}
|
||||
}
|
||||
Value::Object(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue