Browse Source

Implement Array.prototype.reduceRight (#579)

pull/590/head
benjaminflin 4 years ago committed by GitHub
parent
commit
bbd7dd2945
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 97
      boa/src/builtins/array/mod.rs
  2. 123
      boa/src/builtins/array/tests.rs

97
boa/src/builtins/array/mod.rs

@ -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,

123
boa/src/builtins/array/tests.rs

@ -899,6 +899,129 @@ fn reduce() {
assert_eq!(result, "Reduce was called without a callback"); assert_eq!(result, "Reduce was called without a callback");
} }
#[test]
fn reduce_right() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = r#"
var arr = [1, 2, 3, 4];
function sub(acc, x) {
return acc - x;
}
function subIdx(acc, _, idx) {
return acc - idx;
}
function subLen(acc, _x, _idx, arr) {
return acc - arr.length;
}
function subResize(acc, x, idx, arr) {
if(idx == arr.length - 1) {
arr.length = 1;
}
return acc - x;
}
function subResize0(acc, x, idx, arr) {
if(idx == arr.length - 2) {
arr.length = 0;
}
return acc - x;
}
var delArray = [1, 2, 3, 4, 5];
delete delArray[0];
delete delArray[1];
delete delArray[3];
"#;
forward(&mut engine, init);
// empty array
let result = forward(&mut engine, "[].reduceRight(sub, 0)");
assert_eq!(result, "0");
// simple with initial value
let result = forward(&mut engine, "arr.reduceRight(sub, 0)");
assert_eq!(result, "-10");
// without initial value
let result = forward(&mut engine, "arr.reduceRight(sub)");
assert_eq!(result, "-2");
// with some items missing
let result = forward(&mut engine, "delArray.reduceRight(sub, 0)");
assert_eq!(result, "-8");
// with index
let result = forward(&mut engine, "arr.reduceRight(subIdx)");
assert_eq!(result, "1");
// with array
let result = forward(&mut engine, "arr.reduceRight(subLen)");
assert_eq!(result, "-8");
// resizing the array as reduce progresses
let result = forward(&mut engine, "arr.reduceRight(subResize, 0)");
assert_eq!(result, "-5");
// reset array
forward(&mut engine, "arr = [1, 2, 3, 4];");
// resizing the array to 0 as reduce progresses
let result = forward(&mut engine, "arr.reduceRight(subResize0, 0)");
assert_eq!(result, "-7");
// Empty array
let result = forward(
&mut engine,
r#"
try {
[].reduceRight((acc, x) => acc + x);
} catch(e) {
e.message
}
"#,
);
assert_eq!(
result,
"reduceRight was called on an empty array and with no initial value"
);
// Array with no defined elements
let result = forward(
&mut engine,
r#"
try {
var arr = [0, 1];
delete arr[0];
delete arr[1];
arr.reduceRight((acc, x) => acc + x);
} catch(e) {
e.message
}
"#,
);
assert_eq!(
result,
"reduceRight was called on an empty array and with no initial value"
);
// No callback
let result = forward(
&mut engine,
r#"
try {
arr.reduceRight("");
} catch(e) {
e.message
}
"#,
);
assert_eq!(result, "reduceRight was called without a callback");
}
#[test] #[test]
fn call_array_constructor_with_one_argument() { fn call_array_constructor_with_one_argument() {
let realm = Realm::create(); let realm = Realm::create();

Loading…
Cancel
Save