Skip to content

Commit

Permalink
Implement Array.prototype.reduce
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminflin committed Jul 8, 2020
1 parent a933ae8 commit 264878e
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 0 deletions.
72 changes: 72 additions & 0 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,77 @@ impl Array {
Ok(Value::from(false))
}

/// `Array.prototype.reduce( callbackFn [ , initialValue ] )`
///
/// The reduce method traverses left to right starting from the first 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.reduce
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
pub(crate) fn reduce(
this: &Value,
args: &[Value],
interpreter: &mut Interpreter,
) -> ResultValue {
if args.is_empty() {
return Err(Value::from(
"missing callback when calling function Array.prototype.reduce",
));
}
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined);
let mut length = i32::from(&this.get_field("length"));
if length == 0 && initial_value.is_undefined() {
return Err(Value::from(
"Array contains no elements and initial value is not provided",
));
}

let mut k = 0;
let mut accumulator = if initial_value.is_undefined() {
let mut k_present = false;
while k < length {
if this.has_field(&k.to_string()) {
k_present = true;
break;
}
k += 1;
}
if !k_present {
return Err(Value::from(
"Array contains no elements and initial value is not provided",
));
}
let result = this.get_field(k.to_string());
k += 1;
result
} else {
initial_value
};
while k < length {
if this.has_field(&k.to_string()) {
let arguments = [
accumulator,
this.get_field(k.to_string()),
Value::integer(k),
this.clone(),
];
match interpreter.call(&callback, &Value::undefined(), &arguments) {
Ok(value) => accumulator = value,
Err(e) => return Err(e),
}
// reduce may change the length of the array
length = min(length, i32::from(&this.get_field("length")));
}
k += 1;
}
Ok(accumulator)
}

/// Initialise the `Array` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) -> (&str, Value) {
Expand Down Expand Up @@ -988,6 +1059,7 @@ impl Array {
make_builtin_fn(Self::find_index, "findIndex", &prototype, 1);
make_builtin_fn(Self::slice, "slice", &prototype, 2);
make_builtin_fn(Self::some, "some", &prototype, 2);
make_builtin_fn(Self::reduce, "reduce", &prototype, 2);

let array = make_constructor_fn(
Self::NAME,
Expand Down
61 changes: 61 additions & 0 deletions boa/src/builtins/array/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,67 @@ fn some() {
assert_eq!(result, "true");
}

#[test]
fn reduce() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);

let init = r#"
var arr = [1, 2, 3, 4];
function add(acc, x) {
return acc + x;
}
function addIdx(acc, _, idx) {
return acc + idx;
}
function addLen(acc, _x, _idx, arr) {
return acc + arr.length;
}
function addResize(acc, x, idx, arr) {
if(idx == 0) {
arr.length = 3;
}
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, "[].reduce(add, 0)");
assert_eq!(result, "0");

// simple with initial value
let result = forward(&mut engine, "arr.reduce(add, 0)");
assert_eq!(result, "10");

// without initial value
let result = forward(&mut engine, "arr.reduce(add)");
assert_eq!(result, "10");

// with some items missing
let result = forward(&mut engine, "delArray.reduce(add, 0)");
assert_eq!(result, "8");

// with index
let result = forward(&mut engine, "arr.reduce(addIdx, 0)");
assert_eq!(result, "6");

// with array
let result = forward(&mut engine, "arr.reduce(addLen, 0)");
assert_eq!(result, "16");

// resizing the array as reduce progresses
let result = forward(&mut engine, "arr.reduce(addResize, 0)");
assert_eq!(result, "6");
}

#[test]
fn call_array_constructor_with_one_argument() {
let realm = Realm::create();
Expand Down

0 comments on commit 264878e

Please sign in to comment.