|
|
@ -126,9 +126,9 @@ impl Array { |
|
|
|
1 if args[0].is_integer() => { |
|
|
|
1 if args[0].is_integer() => { |
|
|
|
length = i32::from(&args[0]); |
|
|
|
length = i32::from(&args[0]); |
|
|
|
// TODO: It should not create an array of undefineds, but an empty array ("holy" array in V8) with length `n`.
|
|
|
|
// TODO: It should not create an array of undefineds, but an empty array ("holy" array in V8) with length `n`.
|
|
|
|
// for n in 0..length {
|
|
|
|
for n in 0..length { |
|
|
|
// this.set_field(n.to_string(), Value::undefined());
|
|
|
|
this.set_field(n.to_string(), Value::undefined()); |
|
|
|
// }
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
1 if args[0].is_double() => { |
|
|
|
1 if args[0].is_double() => { |
|
|
|
return ctx.throw_range_error("invalid array length"); |
|
|
|
return ctx.throw_range_error("invalid array length"); |
|
|
@ -1022,6 +1022,96 @@ impl Array { |
|
|
|
Ok(accumulator) |
|
|
|
Ok(accumulator) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// `Array.prototype.reduceRight( callbackFn [ , initialValue ] )`
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// The reduceRight method traverses right to left starting from the last defined value in the array,
|
|
|
|
|
|
|
|
/// accumulating a value using a given callback function. It returns the accumulated value.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// More information:
|
|
|
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduceright
|
|
|
|
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight
|
|
|
|
|
|
|
|
pub(crate) fn reduce_right( |
|
|
|
|
|
|
|
this: &Value, |
|
|
|
|
|
|
|
args: &[Value], |
|
|
|
|
|
|
|
interpreter: &mut Interpreter, |
|
|
|
|
|
|
|
) -> ResultValue { |
|
|
|
|
|
|
|
let this = interpreter.to_object(this)?; |
|
|
|
|
|
|
|
let callback = match args.get(0) { |
|
|
|
|
|
|
|
Some(value) if value.is_function() => value, |
|
|
|
|
|
|
|
_ => return interpreter.throw_type_error("reduceRight was called without a callback"), |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined); |
|
|
|
|
|
|
|
let mut length = interpreter.to_length(&this.get_field("length"))?; |
|
|
|
|
|
|
|
if length == 0 { |
|
|
|
|
|
|
|
if initial_value.is_undefined() { |
|
|
|
|
|
|
|
return interpreter.throw_type_error( |
|
|
|
|
|
|
|
"reduceRight was called on an empty array and with no initial value", |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// early return to prevent usize subtraction errors
|
|
|
|
|
|
|
|
return Ok(initial_value); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let mut k = length - 1; |
|
|
|
|
|
|
|
let mut accumulator = if initial_value.is_undefined() { |
|
|
|
|
|
|
|
let mut k_present = false; |
|
|
|
|
|
|
|
loop { |
|
|
|
|
|
|
|
if this.has_field(&k.to_string()) { |
|
|
|
|
|
|
|
k_present = true; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// check must be done at the end to prevent usize subtraction error
|
|
|
|
|
|
|
|
if k == 0 { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
k -= 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if !k_present { |
|
|
|
|
|
|
|
return interpreter.throw_type_error( |
|
|
|
|
|
|
|
"reduceRight was called on an empty array and with no initial value", |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let result = this.get_field(k.to_string()); |
|
|
|
|
|
|
|
k -= 1; |
|
|
|
|
|
|
|
result |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
initial_value |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
loop { |
|
|
|
|
|
|
|
if this.has_field(&k.to_string()) { |
|
|
|
|
|
|
|
let arguments = [ |
|
|
|
|
|
|
|
accumulator, |
|
|
|
|
|
|
|
this.get_field(k.to_string()), |
|
|
|
|
|
|
|
Value::from(k), |
|
|
|
|
|
|
|
this.clone(), |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?; |
|
|
|
|
|
|
|
/* We keep track of possibly shortened length in order to prevent unnecessary iteration.
|
|
|
|
|
|
|
|
It may also be necessary to do this since shortening the array length does not |
|
|
|
|
|
|
|
delete array elements. See: https://github.com/boa-dev/boa/issues/557 */
|
|
|
|
|
|
|
|
length = min(length, interpreter.to_length(&this.get_field("length"))?); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// move k to the last defined element if necessary or return if the length was set to 0
|
|
|
|
|
|
|
|
if k >= length { |
|
|
|
|
|
|
|
if length == 0 { |
|
|
|
|
|
|
|
return Ok(accumulator); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
k = length - 1; |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if k == 0 { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
k -= 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Ok(accumulator) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Initialise the `Array` object on the global object.
|
|
|
|
/// Initialise the `Array` object on the global object.
|
|
|
|
#[inline] |
|
|
|
#[inline] |
|
|
|
pub(crate) fn init(global: &Value) -> (&str, Value) { |
|
|
|
pub(crate) fn init(global: &Value) -> (&str, Value) { |
|
|
@ -1054,6 +1144,7 @@ impl Array { |
|
|
|
make_builtin_fn(Self::slice, "slice", &prototype, 2); |
|
|
|
make_builtin_fn(Self::slice, "slice", &prototype, 2); |
|
|
|
make_builtin_fn(Self::some, "some", &prototype, 2); |
|
|
|
make_builtin_fn(Self::some, "some", &prototype, 2); |
|
|
|
make_builtin_fn(Self::reduce, "reduce", &prototype, 2); |
|
|
|
make_builtin_fn(Self::reduce, "reduce", &prototype, 2); |
|
|
|
|
|
|
|
make_builtin_fn(Self::reduce_right, "reduceRight", &prototype, 2); |
|
|
|
|
|
|
|
|
|
|
|
let array = make_constructor_fn( |
|
|
|
let array = make_constructor_fn( |
|
|
|
Self::NAME, |
|
|
|
Self::NAME, |
|
|
|