Skip to content

Commit

Permalink
Implement Array.prototype.copyWithin
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Jun 18, 2021
1 parent 0825fda commit b957cfd
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 0 deletions.
66 changes: 66 additions & 0 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ impl BuiltIn for Array {
.method(Self::reduce_right, "reduceRight", 2)
.method(Self::keys, "keys", 0)
.method(Self::entries, "entries", 0)
.method(Self::copy_within, "copyWithin", 3)
// Static Methods
.static_method(Self::is_array, "isArray", 1)
.static_method(Self::of, "of", 0)
Expand Down Expand Up @@ -1575,6 +1576,71 @@ impl Array {
Ok(accumulator)
}

/// `Array.prototype.copyWithin ( target, start [ , end ] )`
///
/// The copyWithin() method shallow copies part of an array to another location
/// in the same array and returns it without modifying its length.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.copywithin
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin
pub(crate) fn copy_within(
this: &Value,
args: &[Value],
context: &mut Context,
) -> Result<Value> {
enum Direction {
Forward,
Backward,
}
let this: Value = this.to_object(context)?.into();

let length = this.get_field("length", context)?.to_length(context)?;

let mut to = Self::get_relative_start(context, args.get(0), length)?;
let mut from = Self::get_relative_start(context, args.get(1), length)?;
let finale = Self::get_relative_end(context, args.get(2), length)?;

// saturating sub accounts for the case from > finale, which would cause an overflow
// can skip the check for length - to, because we assert to <= length in get_relative_start
let count = (finale.saturating_sub(from)).min(length - to);

let direction = if from < to && to < from + count {
from += count - 1;
to += count - 1;
Direction::Backward
} else {
Direction::Forward
};

// the original spec uses a while-loop from count to 1,
// but count is not used inside the loop, so we can safely replace it
// with a for-loop from 0 to count - 1
for _ in 0..count {
if this.has_field(from) {
let val = this.get_field(from, context)?;
this.set_field(to, val, true, context)?;
} else {
this.remove_property(to);
}
match direction {
Direction::Forward => {
from += 1;
to += 1;
}
Direction::Backward => {
from -= 1;
to -= 1;
}
}
}

Ok(this)
}

/// `Array.prototype.values( )`
///
/// The values method returns an iterable that iterates over the values in the array.
Expand Down
17 changes: 17 additions & 0 deletions boa/src/builtins/array/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,23 @@ fn concat() {
assert_eq!(nn, "a.b.c");
}

#[test]
fn copy_within() {
let mut context = Context::new();

let target = forward(&mut context, "[1,2,3,4,5].copyWithin(-2).join('.')");
assert_eq!(target, String::from("\"1.2.3.1.2\""));

let start = forward(&mut context, "[1,2,3,4,5].copyWithin(0, 3).join('.')");
assert_eq!(start, String::from("\"4.5.3.4.5\""));

let end = forward(&mut context, "[1,2,3,4,5].copyWithin(0, 3, 4).join('.')");
assert_eq!(end, String::from("\"4.2.3.4.5\""));

let negatives = forward(&mut context, "[1,2,3,4,5].copyWithin(-2, -3, -1).join('.')");
assert_eq!(negatives, String::from("\"1.2.3.3.4\""));
}

#[test]
fn join() {
let mut context = Context::new();
Expand Down

0 comments on commit b957cfd

Please sign in to comment.