avm2: Convert RegExp to ActionScript

This commit is contained in:
Aaron Hill 2023-03-26 22:45:18 -05:00 committed by Mike Welsh
parent 6fda813f0c
commit 27092ecb76
7 changed files with 52 additions and 119 deletions

View File

@ -19,7 +19,6 @@ use swf::avm2::types::{
Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody, Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody,
}; };
use super::method::ParamConfig;
use super::string::AvmString; use super::string::AvmString;
bitflags! { bitflags! {
@ -338,11 +337,10 @@ impl<'gc> Class<'gc> {
let method = Method::from_builtin_and_params( let method = Method::from_builtin_and_params(
table_native_call_handler, table_native_call_handler,
name, name,
vec![ParamConfig::of_type( // A 'callable' class doesn't have a signature - let the
"val", // method do any needed coercions
Multiname::any(activation.context.gc_context), vec![],
)], true,
false,
activation.context.gc_context, activation.context.gc_context,
); );
native_call_handler = Some(method); native_call_handler = Some(method);

View File

@ -32,7 +32,7 @@ mod namespace;
mod number; mod number;
mod object; mod object;
mod qname; mod qname;
mod regexp; mod reg_exp;
mod string; mod string;
mod toplevel; mod toplevel;
mod r#uint; mod r#uint;
@ -523,7 +523,6 @@ pub fn load_player_globals<'gc>(
)?; )?;
function(activation, "", "unescape", toplevel::unescape, script)?; function(activation, "", "unescape", toplevel::unescape, script)?;
avm2_system_class!(regexp, activation, regexp::create_class(activation), script);
avm2_system_class!(vector, activation, vector::create_class(activation), script); avm2_system_class!(vector, activation, vector::create_class(activation), script);
avm2_system_class!(date, activation, date::create_class(activation), script); avm2_system_class!(date, activation, date::create_class(activation), script);
@ -604,6 +603,7 @@ fn load_playerglobal<'gc>(
("", "Error", error), ("", "Error", error),
("", "ArgumentError", argumenterror), ("", "ArgumentError", argumenterror),
("", "RangeError", rangeerror), ("", "RangeError", rangeerror),
("", "RegExp", regexp),
("", "ReferenceError", referenceerror), ("", "ReferenceError", referenceerror),
("", "TypeError", typeerror), ("", "TypeError", typeerror),
("", "VerifyError", verifyerror), ("", "VerifyError", verifyerror),

View File

@ -0,0 +1,31 @@
package {
[Ruffle(InstanceAllocator)]
[Ruffle(CallHandler)]
public dynamic class RegExp {
public function RegExp(re:* = undefined, flags:* = undefined) {
this.init(re, flags)
}
private native function init(re:*, flags:*):void;
public native function get dotall():Boolean;
public native function get extended():Boolean;
public native function get global():Boolean;
public native function get ignoreCase():Boolean;
public native function get multiline():Boolean;
public native function get lastIndex():int;
public native function set lastIndex(value:int):void;
public native function get source():String;
AS3 native function exec(str:String = ""):Object;
AS3 native function test(str:String = ""):Boolean;
prototype.exec = function(str:String = ""):Object {
return this.AS3::exec(str);
}
prototype.test = function(str:String = ""):Boolean {
return this.AS3::test(str);
}
}
}

View File

@ -11,6 +11,7 @@ include "TypeError.as"
include "Math.as" include "Math.as"
include "RangeError.as" include "RangeError.as"
include "ReferenceError.as" include "ReferenceError.as"
include "RegExp.as"
include "SecurityError.as" include "SecurityError.as"
include "SyntaxError.as" include "SyntaxError.as"
include "URIError.as" include "URIError.as"

View File

@ -1,32 +1,22 @@
//! `RegExp` impl //! `RegExp` impl
use crate::avm2::class::Class;
use crate::avm2::error::type_error; use crate::avm2::error::type_error;
use crate::avm2::method::{Method, NativeMethodImpl, ParamConfig}; use crate::avm2::object::{ArrayObject, Object, TObject};
use crate::avm2::object::{regexp_allocator, ArrayObject, FunctionObject, Object, TObject};
use crate::avm2::regexp::RegExpFlags; use crate::avm2::regexp::RegExpFlags;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::QName;
use crate::avm2::{activation::Activation, array::ArrayStorage}; use crate::avm2::{activation::Activation, array::ArrayStorage};
use crate::string::{AvmString, WString}; use crate::string::{AvmString, WString};
use gc_arena::GcCell;
// All of these methods will be defined as both pub use crate::avm2::object::reg_exp_allocator;
// AS3 instance methods and methods on the `Array` class prototype.
const PUBLIC_INSTANCE_AND_PROTO_METHODS: &[(&str, NativeMethodImpl)] =
&[("exec", exec), ("test", test)];
/// Implements `RegExp`'s instance initializer. /// Implements `RegExp`'s `init` method, which is called from the constructor
pub fn instance_init<'gc>( pub fn init<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this { if let Some(this) = this {
activation.super_init(this, &[])?;
if let Some(mut regexp) = this.as_regexp_mut(activation.context.gc_context) { if let Some(mut regexp) = this.as_regexp_mut(activation.context.gc_context) {
let source: AvmString<'gc> = match args.get(0) { let source: AvmString<'gc> = match args.get(0) {
Some(Value::Undefined) => "".into(), Some(Value::Undefined) => "".into(),
@ -76,7 +66,7 @@ pub fn instance_init<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
fn class_call<'gc>( pub fn call_handler<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
_this: Option<Object<'gc>>, _this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
@ -92,39 +82,8 @@ fn class_call<'gc>(
return this_class.construct(activation, args).map(|o| o.into()); return this_class.construct(activation, args).map(|o| o.into());
} }
/// Implements `RegExp`'s class initializer.
pub fn class_init<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this {
let scope = activation.create_scopechain();
let gc_context = activation.context.gc_context;
let this_class = this.as_class_object().unwrap();
let regexp_proto = this_class.prototype();
for (name, method) in PUBLIC_INSTANCE_AND_PROTO_METHODS {
regexp_proto.set_string_property_local(
*name,
FunctionObject::from_method(
activation,
Method::from_builtin(*method, name, gc_context),
scope,
None,
Some(this_class),
)
.into(),
activation,
)?;
regexp_proto.set_local_property_is_enumerable(gc_context, (*name).into(), false);
}
}
Ok(Value::Undefined)
}
/// Implements `RegExp.dotall` /// Implements `RegExp.dotall`
pub fn dotall<'gc>( pub fn get_dotall<'gc>(
_activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -139,7 +98,7 @@ pub fn dotall<'gc>(
} }
/// Implements `RegExp.extended` /// Implements `RegExp.extended`
pub fn extended<'gc>( pub fn get_extended<'gc>(
_activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -154,7 +113,7 @@ pub fn extended<'gc>(
} }
/// Implements `RegExp.global` /// Implements `RegExp.global`
pub fn global<'gc>( pub fn get_global<'gc>(
_activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -169,7 +128,7 @@ pub fn global<'gc>(
} }
/// Implements `RegExp.ignoreCase` /// Implements `RegExp.ignoreCase`
pub fn ignore_case<'gc>( pub fn get_ignore_case<'gc>(
_activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -184,7 +143,7 @@ pub fn ignore_case<'gc>(
} }
/// Implements `RegExp.multiline` /// Implements `RegExp.multiline`
pub fn multiline<'gc>( pub fn get_multiline<'gc>(
_activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -199,7 +158,7 @@ pub fn multiline<'gc>(
} }
/// Implements `RegExp.lastIndex`'s getter /// Implements `RegExp.lastIndex`'s getter
pub fn last_index<'gc>( pub fn get_last_index<'gc>(
_activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -233,7 +192,7 @@ pub fn set_last_index<'gc>(
} }
/// Implements `RegExp.source` /// Implements `RegExp.source`
pub fn source<'gc>( pub fn get_source<'gc>(
_activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
@ -307,59 +266,3 @@ pub fn test<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Construct `RegExp`'s class.
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context;
let class = Class::new(
QName::new(activation.avm2().public_namespace, "RegExp"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")),
Method::from_builtin_and_params(
instance_init,
"<RegExp instance initializer>",
vec![
ParamConfig::optional("re", Multiname::any(mc), Value::Undefined),
ParamConfig::optional("flags", Multiname::any(mc), Value::Undefined),
],
false,
mc,
),
Method::from_builtin(class_init, "<RegExp class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_instance_allocator(regexp_allocator);
write.set_call_handler(Method::from_builtin(
class_call,
"<RegExp call handler>",
mc,
));
const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,
Option<NativeMethodImpl>,
)] = &[
("dotall", Some(dotall), None),
("extended", Some(extended), None),
("global", Some(global), None),
("ignoreCase", Some(ignore_case), None),
("multiline", Some(multiline), None),
("lastIndex", Some(last_index), Some(set_last_index)),
("source", Some(source), None),
];
write.define_builtin_instance_properties(
mc,
activation.avm2().public_namespace,
PUBLIC_INSTANCE_PROPERTIES,
);
write.define_builtin_instance_methods(
mc,
activation.avm2().as3_namespace,
PUBLIC_INSTANCE_AND_PROTO_METHODS,
);
class
}

View File

@ -84,7 +84,7 @@ pub use crate::avm2::object::primitive_object::{primitive_allocator, PrimitiveOb
pub use crate::avm2::object::program_3d_object::Program3DObject; pub use crate::avm2::object::program_3d_object::Program3DObject;
pub use crate::avm2::object::proxy_object::{proxy_allocator, ProxyObject}; pub use crate::avm2::object::proxy_object::{proxy_allocator, ProxyObject};
pub use crate::avm2::object::qname_object::{qname_allocator, QNameObject}; pub use crate::avm2::object::qname_object::{qname_allocator, QNameObject};
pub use crate::avm2::object::regexp_object::{regexp_allocator, RegExpObject}; pub use crate::avm2::object::regexp_object::{reg_exp_allocator, RegExpObject};
pub use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData}; pub use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData};
pub use crate::avm2::object::sound_object::{sound_allocator, QueuedPlay, SoundData, SoundObject}; pub use crate::avm2::object::sound_object::{sound_allocator, QueuedPlay, SoundData, SoundObject};
pub use crate::avm2::object::soundchannel_object::{sound_channel_allocator, SoundChannelObject}; pub use crate::avm2::object::soundchannel_object::{sound_channel_allocator, SoundChannelObject};

View File

@ -12,7 +12,7 @@ use gc_arena::{Collect, GcCell, MutationContext};
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
/// A class instance allocator that allocates RegExp objects. /// A class instance allocator that allocates RegExp objects.
pub fn regexp_allocator<'gc>( pub fn reg_exp_allocator<'gc>(
class: ClassObject<'gc>, class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> { ) -> Result<Object<'gc>, Error<'gc>> {