avm2: Implement `Array.sortOn`

This commit is contained in:
David Wendt 2020-09-05 18:50:43 -04:00 committed by Mike Welsh
parent 0aa2c50118
commit 3b7922d222
6 changed files with 991 additions and 35 deletions

View File

@ -797,9 +797,9 @@ where
/// Sort array storage.
///
/// This function expects it's values to have been pre-enumerated. They will be
/// sorted in-place. It is the caller's responsibility to place the resulting
/// half of the sorted array wherever.
/// This function expects it's values to have been pre-enumerated and
/// pre-resolved. They will be sorted in-place. It is the caller's
/// responsibility to place the resulting half of the sorted array wherever.
///
/// This function will reverse the sort order if `Descending` sort is requested.
///
@ -810,7 +810,7 @@ where
/// contents of the `values` array will be sorted in a random order.
fn sort_inner<'a, 'gc, 'ctxt, C>(
activation: &mut Activation<'a, 'gc, 'ctxt>,
values: &mut [(usize, Option<Value<'gc>>)],
values: &mut [(usize, Value<'gc>)],
options: EnumSet<SortOptions>,
mut sort_func: C,
) -> Result<bool, Error>
@ -821,8 +821,8 @@ where
let mut error_signal = Ok(());
values.sort_unstable_by(|(_a_index, a), (_b_index, b)| {
let unresolved_a = a.clone().unwrap_or(Value::Undefined);
let unresolved_b = b.clone().unwrap_or(Value::Undefined);
let unresolved_a = a.clone();
let unresolved_b = b.clone();
if matches!(unresolved_a, Value::Undefined) && matches!(unresolved_b, Value::Undefined) {
unique_sort_satisfied = false;
@ -833,11 +833,7 @@ where
return Ordering::Less;
}
match sort_func(
activation,
a.clone().unwrap_or(Value::Undefined),
b.clone().unwrap_or(Value::Undefined),
) {
match sort_func(activation, a.clone(), b.clone()) {
Ok(Ordering::Equal) => {
unique_sort_satisfied = false;
Ordering::Equal
@ -897,6 +893,86 @@ fn compare_numeric<'gc>(
}
}
/// Take a sorted set of values and produce the result requested by the caller.
fn sort_postprocess<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
options: EnumSet<SortOptions>,
unique_satisfied: bool,
values: Vec<(usize, Value<'gc>)>,
) -> Result<Value<'gc>, Error> {
if unique_satisfied {
if options.contains(SortOptions::ReturnIndexedArray) {
return build_array(
activation,
ArrayStorage::from_storage(
values
.iter()
.map(|(i, _v)| Some(i.clone().into()))
.collect(),
),
);
} else {
if let Some(mut old_array) = this.as_array_storage_mut(activation.context.gc_context) {
let mut new_vec = Vec::new();
for (src, v) in values.iter() {
if old_array.get(*src).is_none() && !matches!(v, Value::Undefined) {
new_vec.push(Some(v.clone()));
} else {
new_vec.push(old_array.get(*src).clone());
}
}
let mut new_array = ArrayStorage::from_storage(new_vec);
swap(&mut *old_array, &mut new_array);
}
return Ok(this.into());
}
}
Ok(0.into())
}
/// Given a value, extract it's array values.
///
/// If the value is not an array, this function yields `None`.
fn extract_array_values<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>,
) -> Result<Option<Vec<Value<'gc>>>, Error> {
let object = value.coerce_to_object(activation).ok();
let holey_vec = if let Some(object) = object {
if let Some(field_array) = object.as_array_storage() {
let mut array = Vec::new();
for v in field_array.iter() {
array.push(v);
}
array
} else {
return Ok(None);
}
} else {
return Ok(None);
};
let mut unholey_vec = Vec::new();
for (i, v) in holey_vec.iter().enumerate() {
unholey_vec.push(resolve_array_hole(
activation,
object.unwrap(),
i,
v.clone(),
)?);
}
Ok(Some(unholey_vec))
}
/// Impl `Array.sort`
pub fn sort<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
@ -927,16 +1003,17 @@ pub fn sort<'gc>(
)
};
let mut values = if let Some(array) = this.as_array_storage() {
array
let mut values = if let Some(values) = extract_array_values(activation, this.into())? {
values
.iter()
.enumerate()
.collect::<Vec<(usize, Option<Value<'gc>>)>>()
.map(|(i, v)| (i, v.clone()))
.collect::<Vec<(usize, Value<'gc>)>>()
} else {
return Ok(0.into());
};
let unique_satisified = if let Some(v) = compare_fnc {
let unique_satisfied = if let Some(v) = compare_fnc {
sort_inner(
activation,
&mut values,
@ -973,29 +1050,145 @@ pub fn sort<'gc>(
)?
};
if unique_satisified {
if options.contains(SortOptions::ReturnIndexedArray) {
return build_array(
activation,
ArrayStorage::from_storage(
values
.iter()
.map(|(i, _v)| Some(i.clone().into()))
.collect(),
),
);
return sort_postprocess(activation, this, options, unique_satisfied, values);
}
Ok(0.into())
}
/// Given a value, extract it's array values.
///
/// If the value is not an array, it will be returned as if it was present in a
/// one-element array containing itself. This is intended for use with parsing
/// parameters which are optionally arrays.
fn extract_maybe_array_values<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>,
) -> Result<Vec<Value<'gc>>, Error> {
Ok(extract_array_values(activation, value.clone())?.unwrap_or_else(|| vec![value]))
}
/// Given a value, extract it's array values and coerce them to strings.
///
/// If the value is not an array, it will be returned as if it was present in a
/// one-element array containing itself. This is intended for use with parsing
/// parameters which are optionally arrays. The returned value will still be
/// coerced into a string in this case.
fn extract_maybe_array_strings<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>,
) -> Result<Vec<AvmString<'gc>>, Error> {
let mut out = Vec::new();
for value in extract_maybe_array_values(activation, value)? {
out.push(value.coerce_to_string(activation)?);
}
Ok(out)
}
/// Given a value, extract it's array values and coerce them to enumsets.
///
/// If the value is not an array, it will be returned as if it was present in a
/// one-element array containing itself. This is intended for use with parsing
/// parameters which are optionally arrays. The returned value will still be
/// coerced into a string in this case.
fn extract_maybe_array_enumsets<'gc, E>(
activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>,
) -> Result<Vec<EnumSet<E>>, Error>
where
E: EnumSetType,
{
let mut out = Vec::new();
for value in extract_maybe_array_values(activation, value)? {
out.push(value.coerce_to_enumset(activation)?);
}
Ok(out)
}
/// Impl `Array.sortOn`
pub fn sort_on<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
if let Some(field_names_value) = args.get(0).cloned() {
let field_names = extract_maybe_array_strings(activation, field_names_value)?;
let mut options = extract_maybe_array_enumsets(
activation,
args.get(1).cloned().unwrap_or_else(|| 0.into()),
)?;
let first_option = options
.get(0)
.cloned()
.unwrap_or_else(EnumSet::empty)
.intersection(SortOptions::UniqueSort | SortOptions::ReturnIndexedArray);
let mut values = if let Some(values) = extract_array_values(activation, this.into())? {
values
.iter()
.enumerate()
.map(|(i, v)| (i, v.clone()))
.collect::<Vec<(usize, Value<'gc>)>>()
} else {
let mut new_array =
ArrayStorage::from_storage(values.iter().map(|(_i, v)| v.clone()).collect());
return Ok(0.into());
};
if let Some(mut old_array) =
this.as_array_storage_mut(activation.context.gc_context)
{
swap(&mut *old_array, &mut new_array);
}
return Ok(this.into());
if options.len() < field_names.len() {
options.resize(
field_names.len(),
options.last().cloned().unwrap_or_else(EnumSet::empty),
);
}
let unique_satisfied = sort_inner(
activation,
&mut values,
first_option,
constrain(|activation, a, b| {
for (field_name, options) in field_names.iter().zip(options.iter()) {
let mut a_object = a.coerce_to_object(activation)?;
let a_field = a_object.get_property(
a_object,
&QName::new(Namespace::public_namespace(), *field_name),
activation,
)?;
let mut b_object = b.coerce_to_object(activation)?;
let b_field = b_object.get_property(
b_object,
&QName::new(Namespace::public_namespace(), *field_name),
activation,
)?;
let ord = if options.contains(SortOptions::Numeric) {
compare_numeric(activation, a_field, b_field)?
} else if options.contains(SortOptions::CaseInsensitive) {
compare_string_case_insensitive(activation, a_field, b_field)?
} else {
compare_string_case_sensitive(activation, a_field, b_field)?
};
if matches!(ord, Ordering::Equal) {
continue;
}
if options.contains(SortOptions::Descending) {
return Ok(ord.reverse());
} else {
return Ok(ord);
}
}
Ok(Ordering::Equal)
}),
)?;
return sort_postprocess(activation, this, first_option, unique_satisfied, values);
}
}
@ -1117,6 +1310,11 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
Method::from_builtin(sort),
));
class.write(mc).define_instance_trait(Trait::from_method(
QName::new(Namespace::public_namespace(), "sortOn"),
Method::from_builtin(sort_on),
));
class.write(mc).define_class_trait(Trait::from_const(
QName::new(Namespace::public_namespace(), "CASEINSENSITIVE"),
Multiname::from(QName::new(Namespace::public_namespace(), "uint")),

View File

@ -380,6 +380,7 @@ swf_tests! {
(as3_array_slice, "avm2/array_slice", 1),
(as3_array_splice, "avm2/array_splice", 1),
(as3_array_sort, "avm2/array_sort", 1),
(as3_array_sorton, "avm2/array_sorton", 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,218 @@
package {
public class Test {
}
}
function assert_array(a) {
for (var i = 0; i < a.length; i += 1) {
trace(a[i]);
}
}
function assert_array_props(a) {
for (var i = 0; i < a.length; i += 1) {
if (a[i] !== undefined && a[i] !== null) {
trace(a[i].numprop);
trace(a[i].strprop);
} else {
trace("//(undefined value omitted)");
}
}
}
function fresh_array_a() {
trace("//var a = new Array(item1, item2, item3)");
var a = new Array(item1, item2, item3);
trace("//a[4] = item5;");
a[4] = item5;
return a;
}
function test_holes(a) {
var item4 = {"numprop": 9, "strprop": "boo", "numprop2": 4};
trace("//Array.prototype[2] = \"hole10\";");
Array.prototype[2] = "hole10";
trace("//Array.prototype[3] = \"hole11\";");
Array.prototype[3] = "hole11";
trace("//Array.prototype[4] = \"hole12\";");
Array.prototype[4] = "hole12";
trace("//(properties of contents of a)");
assert_array_props(a);
trace("//(cleaning up our holes...)");
delete Array.prototype[2];
delete Array.prototype[4];
trace("//Array.prototype[3] = item4;");
Array.prototype[3] = item4;
}
trace("//var item1 = {\"numprop\": 3, \"strprop\": \"Abc\", \"numprop2\": 3}");
var item1 = {"numprop": 5, "strprop": "Abc", "numprop2": 3};
trace("//var item2 = {\"numprop\": 3, \"strprop\": \"Azc\", \"numprop2\": 2}");
var item2 = {"numprop": 3, "strprop": "Azc", "numprop2": 2};
trace("//var item3 = {\"numprop\": 3, \"strprop\": \"aXc\", \"numprop2\": 1}");
var item3 = {"numprop": 7, "strprop": "aXc", "numprop2": 1};
trace("//var item4 = {\"numprop\": 3, \"strprop\": \"boo\", \"numprop2\": 4}");
var item4 = {"numprop": 9, "strprop": "boo", "numprop2": 4};
trace("//var item5 = {\"numprop\": 5, \"strprop\": \"bool\", \"numprop2\": \"5\"}");
var item5 = {"numprop": 11, "strprop": "bool", "numprop2": "5"};
var a = fresh_array_a();
trace("//Array.prototype[3] = item4;");
Array.prototype[3] = item4;
trace("//a.sortOn(\"numprop\", Array.UNIQUESORT) === 0");
trace(a.sortOn("numprop", Array.UNIQUESORT) === 0);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.RETURNINDEXEDARRAY))");
assert_array(a.sortOn(["numprop", "strprop"], Array.RETURNINDEXEDARRAY));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"]))");
assert_array_props(a.sortOn(["numprop", "strprop"]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE | Array.RETURNINDEXEDARRAY))");
assert_array(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.RETURNINDEXEDARRAY));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE))");
assert_array_props(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING | Array.RETURNINDEXEDARRAY))");
assert_array(a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.RETURNINDEXEDARRAY));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING))");
assert_array_props(a.sortOn(["numprop", "strprop"], Array.DESCENDING));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE | Array.DESCENDING | Array.RETURNINDEXEDARRAY))");
assert_array(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING | Array.RETURNINDEXEDARRAY));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE | Array.DESCENDING))");
assert_array_props(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.NUMERIC | Array.RETURNINDEXEDARRAY))");
assert_array(a.sortOn(["numprop", "strprop"], Array.NUMERIC | Array.RETURNINDEXEDARRAY));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.NUMERIC))");
assert_array_props(a.sortOn(["numprop", "strprop"], Array.NUMERIC));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING | Array.NUMERIC | Array.RETURNINDEXEDARRAY))");
assert_array(a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC | Array.RETURNINDEXEDARRAY));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING | Array.NUMERIC))");
assert_array_props(a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, 0]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, 0]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, 0]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [0, 0]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, Array.DESCENDING]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.DESCENDING]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, Array.DESCENDING]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [0, Array.DESCENDING]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, 0]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, 0]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.DESCENDING, 0]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [Array.DESCENDING, 0]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, Array.CASEINSENSITIVE]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, 0]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, 0]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.CASEINSENSITIVE, 0]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, 0]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE | Array.DESCENDING]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE | Array.DESCENDING]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, Array.CASEINSENSITIVE | Array.DESCENDING]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE | Array.DESCENDING]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE | Array.DESCENDING, 0]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE | Array.DESCENDING, 0]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.CASEINSENSITIVE | Array.DESCENDING, 0]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE | Array.DESCENDING, 0]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, Array.CASEINSENSITIVE]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, Array.CASEINSENSITIVE]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.DESCENDING, Array.CASEINSENSITIVE]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [Array.DESCENDING, Array.CASEINSENSITIVE]));
test_holes(a);
a = fresh_array_a();
trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, Array.DESCENDING]))");
assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, Array.DESCENDING]));
trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.CASEINSENSITIVE, Array.DESCENDING]))");
assert_array_props(a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, Array.DESCENDING]));
test_holes(a);
a = fresh_array_a();
trace("//a.sortOn([\"strprop\", \"numprop\"], [Array.NUMERIC, Array.UNIQUESORT]) === 0");
trace(a.sortOn(["strprop", "numprop"], [Array.NUMERIC, Array.UNIQUESORT]) === 0);

View File

@ -0,0 +1,539 @@
//var item1 = {"numprop": 3, "strprop": "Abc", "numprop2": 3}
//var item2 = {"numprop": 3, "strprop": "Azc", "numprop2": 2}
//var item3 = {"numprop": 3, "strprop": "aXc", "numprop2": 1}
//var item4 = {"numprop": 3, "strprop": "boo", "numprop2": 4}
//var item5 = {"numprop": 5, "strprop": "bool", "numprop2": "5"}
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//Array.prototype[3] = item4;
//a.sortOn("numprop", Array.UNIQUESORT) === 0
false
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], Array.RETURNINDEXEDARRAY))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"]))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.RETURNINDEXEDARRAY))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.RETURNINDEXEDARRAY))
3
2
0
1
4
//(properties of contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING))
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING | Array.RETURNINDEXEDARRAY))
3
2
0
1
4
//(properties of contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING))
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], Array.NUMERIC | Array.RETURNINDEXEDARRAY))
1
0
2
3
4
//(properties of contents of a.sortOn(["numprop", "strprop"], Array.NUMERIC))
3
Azc
5
Abc
7
aXc
9
boo
11
bool
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
3
Azc
5
Abc
7
aXc
9
boo
11
bool
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC | Array.RETURNINDEXEDARRAY))
4
3
2
0
1
//(properties of contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC))
11
bool
9
boo
7
aXc
5
Abc
3
Azc
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
9
boo
7
aXc
5
Abc
3
Azc
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, 0]))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"], [0, 0]))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.DESCENDING]))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"], [0, Array.DESCENDING]))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, 0]))
3
2
0
1
4
//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.DESCENDING, 0]))
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE]))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE]))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, 0]))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, 0]))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE | Array.DESCENDING]))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE | Array.DESCENDING]))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE | Array.DESCENDING, 0]))
3
2
0
1
4
//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE | Array.DESCENDING, 0]))
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, Array.CASEINSENSITIVE]))
3
2
0
1
4
//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.DESCENDING, Array.CASEINSENSITIVE]))
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
9
boo
7
aXc
5
Abc
3
Azc
11
bool
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, Array.DESCENDING]))
4
1
0
2
3
//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, Array.DESCENDING]))
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//Array.prototype[2] = "hole10";
//Array.prototype[3] = "hole11";
//Array.prototype[4] = "hole12";
//(properties of contents of a)
11
bool
3
Azc
5
Abc
7
aXc
9
boo
//(cleaning up our holes...)
//Array.prototype[3] = item4;
//var a = new Array(item1, item2, item3)
//a[4] = item5;
//a.sortOn(["strprop", "numprop"], [Array.NUMERIC, Array.UNIQUESORT]) === 0
false

Binary file not shown.

Binary file not shown.