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 3 commits
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 {
let this = interpreter.to_object(this)?;
let callback = match args.get(0) {
Some(value) if value.is_function() => value,
_ => {
return interpreter.throw_type_error(
"missing callback when calling function Array.prototype.reduce",
)
}
};
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 && initial_value.is_undefined() {
return interpreter
.throw_type_error("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 interpreter.throw_type_error(
"Array contains no elements and initial value is not provided",
Razican 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::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"))?);
}
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