diff --git a/core/build_playerglobal/src/lib.rs b/core/build_playerglobal/src/lib.rs index bb76e685a..9198602f7 100644 --- a/core/build_playerglobal/src/lib.rs +++ b/core/build_playerglobal/src/lib.rs @@ -1,7 +1,7 @@ //! An internal Ruffle utility to build our playerglobal //! `library.swf` -use convert_case::{Case, Casing}; +use convert_case::{Boundary, Case, Casing}; use proc_macro2::TokenStream; use quote::quote; use std::fs::File; @@ -109,7 +109,13 @@ fn flash_to_rust_path(path: &str) -> String { // so 'URLLoader' becomes 'url_loader' let components = path .split('.') - .map(|component| component.to_case(Case::Snake)) + .map(|component| { + component + .from_case(Case::Camel) + // Do not split on a letter followed by a digit, so e.g. `atan2` won't become `atan_2`. + .without_boundaries(&[Boundary::UpperDigit, Boundary::LowerDigit]) + .to_case(Case::Snake) + }) .collect::>(); // Form a Rust path from the snake-case components components.join("::") diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 7129ade79..58b076e8e 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -432,7 +432,6 @@ pub fn load_player_globals<'gc>( object_class, )?; - class(activation, math::create_class(mc), script)?; class(activation, json::create_class(mc), script)?; avm2_system_class!(regexp, activation, regexp::create_class(mc), script); avm2_system_class!(vector, activation, vector::create_class(mc), script); diff --git a/core/src/avm2/globals/Math.as b/core/src/avm2/globals/Math.as new file mode 100644 index 000000000..a14787321 --- /dev/null +++ b/core/src/avm2/globals/Math.as @@ -0,0 +1,38 @@ +package { + public final class Math { + public static const E: Number = 2.718281828459045; + public static const LN10: Number = 2.302585092994046; + public static const LN2: Number = 0.6931471805599453; + public static const LOG10E: Number = 0.4342944819032518; + public static const LOG2E: Number = 1.442695040888963387; + public static const PI: Number = 3.141592653589793; + public static const SQRT1_2: Number = 0.7071067811865476; + public static const SQRT2: Number = 1.4142135623730951; + + public static native function abs(x: Number): Number; + public static native function acos(x: Number): Number; + public static native function asin(x: Number): Number; + public static native function atan(x: Number): Number; + public static native function ceil(x: Number): Number; + public static native function cos(x: Number): Number; + public static native function exp(x: Number): Number; + public static native function floor(x: Number): Number; + public static native function log(x: Number): Number; + public static native function round(x: Number): Number; + public static native function sin(x: Number): Number; + public static native function sqrt(x: Number): Number; + public static native function tan(x: Number): Number; + public static native function atan2(y: Number, x: Number): Number; + public static native function pow(x: Number, y: Number): Number; + + // This is a hacky way to specify `-Infinity` as a default value. + private static const NegInfinity: Number = -1 / 0; + public static native function max(x: Number = NegInfinity, y: Number = NegInfinity, ...rest): Number; + + // TODO: Remove this once `Infinity` is properly defined in ActionScript. + private static const Infinity: Number = 1 / 0; + public static native function min(x: Number = Infinity, y: Number = Infinity, ...rest): Number; + + public static native function random(): Number; + } +} diff --git a/core/src/avm2/globals/globals.as b/core/src/avm2/globals/globals.as index 6e1c118de..c4e69ae55 100644 --- a/core/src/avm2/globals/globals.as +++ b/core/src/avm2/globals/globals.as @@ -2,6 +2,7 @@ // need to come before subclasses. include "Error.as" +include "Math.as" include "flash/accessibility/AccessibilityProperties.as" include "flash/crypto.as" diff --git a/core/src/avm2/globals/math.rs b/core/src/avm2/globals/math.rs index 27b2b0bd8..791fe9e90 100644 --- a/core/src/avm2/globals/math.rs +++ b/core/src/avm2/globals/math.rs @@ -1,19 +1,18 @@ //! `Math` impl use crate::avm2::activation::Activation; -use crate::avm2::class::{Class, ClassAttributes}; -use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::avm2::Namespace; -use crate::avm2::QName; -use gc_arena::{GcCell, MutationContext}; use rand::Rng; -macro_rules! math_wrap_std { - ($std:expr) => { - |activation, _this, args| -> Result, Error> { +macro_rules! wrap_std { + ($name:ident, $std:expr) => { + pub fn $name<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + args: &[Value<'gc>], + ) -> Result, Error> { if let Some(input) = args.get(0) { Ok($std(input.coerce_to_number(activation)?).into()) } else { @@ -23,77 +22,20 @@ macro_rules! math_wrap_std { }; } -/// Implements `Math`'s instance initializer. -pub fn instance_init<'gc>( - _activation: &mut Activation<'_, 'gc, '_>, - _this: Option>, - _args: &[Value<'gc>], -) -> Result, Error> { - // TODO: Replace with actual error type. - Err("TypeError: Error #1076: Math is not a constructor.".into()) -} +wrap_std!(abs, f64::abs); +wrap_std!(acos, f64::acos); +wrap_std!(asin, f64::asin); +wrap_std!(atan, f64::atan); +wrap_std!(ceil, f64::ceil); +wrap_std!(cos, f64::cos); +wrap_std!(exp, f64::exp); +wrap_std!(floor, f64::floor); +wrap_std!(log, f64::ln); +wrap_std!(sin, f64::sin); +wrap_std!(sqrt, f64::sqrt); +wrap_std!(tan, f64::tan); -/// Implements `Math`'s class initializer. -pub fn class_init<'gc>( - _activation: &mut Activation<'_, 'gc, '_>, - _this: Option>, - _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined) -} - -/// Construct `Math`'s class. -pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { - let class = Class::new( - QName::new(Namespace::public(), "Math"), - Some(QName::new(Namespace::public(), "Object").into()), - Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), - mc, - ); - - let mut write = class.write(mc); - write.set_attributes(ClassAttributes::FINAL | ClassAttributes::SEALED); - - use std::f64::consts::*; - const CONSTANTS: &[(&str, f64)] = &[ - ("E", E), - ("LN10", LN_10), - ("LN2", LN_2), - ("LOG10E", LOG10_E), - ("LOG2E", LOG2_E), - ("PI", PI), - ("SQRT1_2", FRAC_1_SQRT_2), - ("SQRT2", SQRT_2), - ]; - write.define_public_constant_number_class_traits(CONSTANTS); - - const PUBLIC_CLASS_METHODS: &[(&str, NativeMethodImpl)] = &[ - ("atan2", atan2), - ("max", max), - ("min", min), - ("pow", pow), - ("random", random), - ("round", round), - ("abs", math_wrap_std!(f64::abs)), - ("acos", math_wrap_std!(f64::acos)), - ("asin", math_wrap_std!(f64::asin)), - ("atan", math_wrap_std!(f64::atan)), - ("ceil", math_wrap_std!(f64::ceil)), - ("cos", math_wrap_std!(f64::cos)), - ("exp", math_wrap_std!(f64::exp)), - ("floor", math_wrap_std!(f64::floor)), - ("log", math_wrap_std!(f64::ln)), - ("sin", math_wrap_std!(f64::sin)), - ("sqrt", math_wrap_std!(f64::sqrt)), - ("tan", math_wrap_std!(f64::tan)), - ]; - write.define_public_builtin_class_methods(mc, PUBLIC_CLASS_METHODS); - - class -} - -fn round<'gc>( +pub fn round<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Option>, args: &[Value<'gc>], @@ -108,7 +50,7 @@ fn round<'gc>( Ok(f64::NAN.into()) } -fn atan2<'gc>( +pub fn atan2<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Option>, args: &[Value<'gc>], @@ -124,7 +66,7 @@ fn atan2<'gc>( Ok(f64::atan2(y, x).into()) } -fn max<'gc>( +pub fn max<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Option>, args: &[Value<'gc>], @@ -141,7 +83,7 @@ fn max<'gc>( Ok(cur_max.into()) } -fn min<'gc>( +pub fn min<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Option>, args: &[Value<'gc>], @@ -158,7 +100,7 @@ fn min<'gc>( Ok(cur_min.into()) } -fn pow<'gc>( +pub fn pow<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Option>, args: &[Value<'gc>],