core: Merge #91, add some global conversion functions

This commit is contained in:
Mike Welsh 2019-10-21 02:29:51 -07:00 committed by GitHub
commit 6bd4ed22a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 245 additions and 52 deletions

View File

@ -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()?;

View File

@ -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)
);
}

View File

@ -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)]),

View File

@ -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)
})
}

View File

@ -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,
}
}