avm2: Implement String.charAt

This commit is contained in:
paq 2021-02-06 19:50:08 +09:00 committed by Mike Welsh
parent 2ec21bdd07
commit c7c025277f
6 changed files with 98 additions and 0 deletions

View File

@ -4,9 +4,11 @@ use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, TObject};
use crate::avm2::string::AvmString;
use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::avm2::{activation::Activation, traits::Trait};
use crate::string_utils;
use gc_arena::{GcCell, MutationContext};
/// Implements `String`'s instance initializer.
@ -54,6 +56,36 @@ fn length<'gc>(
Ok(Value::Undefined)
}
/// Implements `String.charAt`
fn char_at<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
if let Value::String(s) = this.value_of(activation.context.gc_context)? {
// This function takes Number, so if we use coerce_to_i32 instead of coerce_to_number, the value may overflow.
let n = args
.get(0)
.unwrap_or(&Value::Number(0.0))
.coerce_to_number(activation)?;
if n < 0.0 {
return Ok("".into());
}
let index = if !n.is_nan() { n as usize } else { 0 };
let ret = s
.encode_utf16()
.nth(index)
.map(|c| string_utils::utf16_code_unit_to_char(c).to_string())
.unwrap_or_default();
return Ok(AvmString::new(activation.context.gc_context, ret).into());
}
}
Ok(Value::Undefined)
}
/// Construct `String`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new(
@ -72,5 +104,10 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
Method::from_builtin(length),
));
write.define_instance_trait(Trait::from_method(
QName::new(Namespace::as3_namespace(), "charAt"),
Method::from_builtin(char_at),
));
class
}

View File

@ -506,6 +506,7 @@ swf_tests! {
(as3_movieclip_dispatchevent_selfadd, "avm2/movieclip_dispatchevent_selfadd", 1),
(as3_string_constr, "avm2/string_constr", 1),
(as3_string_length, "avm2/string_length", 1),
(as3_string_char_at, "avm2/string_char_at", 1),
}
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.

View File

@ -0,0 +1,32 @@
package {
public class Test {}
}
trace("//\"abcdefg\".charAt();")
trace("abcdefg".charAt());
trace("//\"abcdefg\".charAt(1);")
trace("abcdefg".charAt(1));
trace("//\"abcdefg\".charAt(1.1);")
trace("abcdefg".charAt(1.1));
trace("//\"abcdefg\".charAt(1.5);")
trace("abcdefg".charAt(1.5));
trace("//\"abcdefg\".charAt(7);")
trace("abcdefg".charAt(7));
trace("//\"abcdefg\".charAt(-1);")
trace("abcdefg".charAt(-1));
trace("//\"abcdefg\".charAt(NaN);")
trace("abcdefg".charAt(NaN));
trace("//\"abcdefg\".charAt(1.79e+308);")
trace("abcdefg".charAt(1.79e+308));
trace("//\"abcdefg\".charAt(Infinity);")
trace("abcdefg".charAt(Infinity));
trace("//\"abcdefg\".charAt(-Infinity);")
trace("abcdefg".charAt(-Infinity));
trace("//\"あいうえお\".charAt(1);")
trace("あいうえお".charAt(1));
trace("//\"مَرحَبًا\".charAt(1);")
trace("مَرحَبًا".charAt(1));
trace("//\"👨‍👨‍👧‍👦\".charAt(0);")
trace("👨‍👨‍👧‍👦".charAt(0));
trace("//\"\".charAt(0);")
trace("".charAt(0));

View File

@ -0,0 +1,28 @@
//"abcdefg".charAt();
a
//"abcdefg".charAt(1);
b
//"abcdefg".charAt(1.1);
b
//"abcdefg".charAt(1.5);
b
//"abcdefg".charAt(7);
//"abcdefg".charAt(-1);
//"abcdefg".charAt(NaN);
a
//"abcdefg".charAt(1.79e+308);
//"abcdefg".charAt(Infinity);
//"abcdefg".charAt(-Infinity);
//"あいうえお".charAt(1);
//"مَرحَبًا".charAt(1);
َ
//"👨‍👨‍👧‍👦".charAt(0);
<EFBFBD>
//"".charAt(0);

Binary file not shown.

Binary file not shown.