Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Array.prototype.reduce #555

Merged
merged 4 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
benjaminflin marked this conversation as resolved.
Show resolved Hide resolved
return Err(Value::from(
"missing callback when calling function Array.prototype.reduce",
));
}
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
benjaminflin marked this conversation as resolved.
Show resolved Hide resolved
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined);
let mut length = i32::from(&this.get_field("length"));
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved
if length == 0 && initial_value.is_undefined() {
return Err(Value::from(
"Array contains no elements and initial value is not provided",
));
benjaminflin marked this conversation as resolved.
Show resolved Hide resolved
}

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",
Razican marked this conversation as resolved.
Show resolved Hide resolved
));
benjaminflin marked this conversation as resolved.
Show resolved Hide resolved
}
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),
}
benjaminflin marked this conversation as resolved.
Show resolved Hide resolved
// reduce may change the length of the array
length = min(length, i32::from(&this.get_field("length")));
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved
}
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